【经典算法】动态规划

二次信任 提交于 2020-01-04 01:03:47

在这里插入图片描述

斐波那契数列的启发

在讨论动态规划之前,我们先看一个熟悉的例子——斐波那契数列(Fibonacci sequence):

CSDN图标

如果我想求f(3)f(3),那我只要知道f(1)f(1)f(2)f(2)就可以了,同理,如果想知道f(4)f(4),那我需要知道f(2)f(2)f(3)f(3)。以此类推,如果我想知道f(n)f(n),那么只要已知f(1)...f(n1)f(1)...f(n-1)即可。对于像具有像斐波那契数列这样递推关系的问题,都可以用这种方法求解。虽然这样看上去有点笨,却是一个用空间换时间的高效方法,因为这样时间复杂度就能降到O(n)O(n),而用递归的方法时间复杂度可能达到O(2n)O(2^n)。下面我们看一个例子:

第一个例子

对于数组[a1 a2 ... an][a_1\ a_2 \ ... \ a_n],求出不相邻元素的最大和。

将问题具体化,我们求[1 2 4 1 7 8 3](如下图上列为下标号,下列为元素)的不相邻元素的最大和。从后往前推,arr[6]=3,它的最近不相邻元素的是arr[4]=7。这时有两种可能,一种选arr[6],则sum=arr[4]+arr[6],另一种是不选arr[6],直接看arr[5]和它的不相邻元素。
在这里插入图片描述
为了方便描述,我们用函数rec_opt()来描述递归选择的过程,如下图所示。

CSDN图标

明显这是一个递归关系,递归的出口是rec_opt(0)=1,rec_opt(1)=max(arr[0],arr[1])。这样我们可以写出递归代码:

static int rec_opt(int[] arr,int i)
	{//i表示开始递归的位置,本例从arr的最后一个元素开始递归
		if(i==0)
			return arr[0];
		if (i==1)
			return Math.max(arr[1],arr[0]); 
		//选1则返回arr[1],不选1则返回arr[0]
		else 
			return Math.max(rec_opt(arr, i-1),rec_opt(arr, i-2)+arr[i]);
	}

但这样的方法时间复杂度为O(2n)O(2^n),且占用内存大。我们观察递归算法的二叉树形式,会发现上层的rec_opt都是用rec_opt(0)、rec_opt(1)累积出来的。如果我们创建一个数组,记录从rec_opt(0)、rec_opt(1)到rec_opt(i)的所有值,就不用再重复调用rec_opt了。
在这里插入图片描述
于是我们可以得出非递归的算法:

static int dp_opt(int[] arr)
	{
		int []temp= new int[arr.length];
		temp[0]=arr[0];
		temp[1]=arr[1];
		for (int j=2;j<arr.length;j++) {
			temp[j]=Math.max(temp[j-2]+arr[j],temp[j-1]);
		}
		return temp[arr.length-1];
	}

我们再看另外一个例子:

第二个例子

判断一个序列是否存在和等于s的子列。

在这里插入图片描述
设序列为[3,34,4,12,5,23],s=9。还是按照之前的思路,从最后一个元素开始向前遍历。如23大于9,则跳过,5小于9,则s=s-5=4。当s=0时,说明找到了要找的子列。如果所有都遍历完s还不为0,则没找到。因此我们可以得到如下的递归算法:

static boolean rec_series(int[] arr,int i,int s)
	{
		if (s==0) {
		//如果正好没有剩余,则返回true
			return true;
		}
		else if (i==0) {
		//若正好遍历到最后一个,当最后一个就是s时,返回true
			return arr[i]==s;
		} 
		else if (arr[i]>s) {
		//如果第i个比s大,只能不选择
			return rec_series(arr,i-1,s);
		}
		else {
		//上述情况都不存在,可以选择第i个元素,也可以跳到第i-1个元素
			return rec_series(arr,i-1,s-arr[i]) 
				|| rec_series(arr,i-1,s);
		}
	}

同样,我们可以借鉴前面的思想,列出如下图所示的二维表,记录rec_series(arr,i,s)的结果。其中第一行是循环开始前初始化的内容。由于后面的结果依赖前面,所以i=5,s=9就是最终的结果。
在这里插入图片描述
以下为动态规划的算法:

static boolean dp_series(int[] arr,int s)
	{
		boolean [][]b=new boolean[arr.length][s];
		for (int i=0;i<s;i++) {
		//初始化二维表的第一行
			if(arr[0]==i) {
				b[0][i]=true;
			}
		}
		
		for (int i=1;i<arr.length;i++) {
		//把递归过程变成查表过程
			for(int j=0;j<s;j++)
			{
				if(j==arr[i]){
					b[i][j]=true;
				}
				else if(j<arr[i]) {
					b[i][j]=b[i-1][j];
				}
				else {
					b[i][j]=b[i-1][j-arr[i]-1];
				}
			}
		}
		return b[arr.length-1][s-1];
	}

参考资料

动态规划 (第1讲)
动态规划 (第2讲)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!