斐波那契数列的启发
在讨论动态规划之前,我们先看一个熟悉的例子——斐波那契数列(Fibonacci sequence):
如果我想求,那我只要知道和就可以了,同理,如果想知道,那我需要知道和。以此类推,如果我想知道,那么只要已知即可。对于像具有像斐波那契数列这样递推关系的问题,都可以用这种方法求解。虽然这样看上去有点笨,却是一个用空间换时间的高效方法,因为这样时间复杂度就能降到,而用递归的方法时间复杂度可能达到。下面我们看一个例子:
第一个例子
对于数组,求出不相邻元素的最大和。
将问题具体化,我们求[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()来描述递归选择的过程,如下图所示。
明显这是一个递归关系,递归的出口是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]);
}
但这样的方法时间复杂度为,且占用内存大。我们观察递归算法的二叉树形式,会发现上层的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];
}
参考资料
来源:CSDN
作者:达拉斯老妖
链接:https://blog.csdn.net/vvfgfg243497546/article/details/103812648