在考虑动态规划的时候,可以把问题简化为选和不选的问题,并把问题抛给下一轮的选和不选。小偷入室偷窃就是一类经典的基本动态规划问题—如何在选取一个数而不再取相邻数字相加最大和
有一个目标数组array=[1,2,3,1];
·如果选择array[0]=1 那么array[1]则不再选取 抛出下一轮选和不选的问题 有一个目标数组array=[3,1];
·如果不选择array[0] 那么array[1]=2可以选取 抛出下一轮问题 有一个目标数组array=[2,3,1];
对于目标数组array[3,1];
·如果选择array[0]=3 那么array[1]则不再选取 抛出下一轮选和不选的问题 有一个目标数组array=[];
目标数组长度为0返回最终结果 sum=4;
·如果不选择array[0] 那么array[1]=1可以选取 抛出下一轮问题 有一个目标数组array=[1];
对于目标数组array[2,3,1];
·如果选择array[0]=2 那么array[1]则不再选取 抛出下一轮选和不选的问题 有一个目标数组array=[1];
·如果不选择array[0] 那么array[1]=3可以选取 抛出下一轮问题 有一个目标数组array=[];
目标数组长度为0返回最终结果 sum=3;
.....
.....
…动态规划实际上就是一轮接着一轮的套娃,并返回最终结果的最大值,选和不选的经过不需要我们详细地去了解,这一切只需要把它抛给计算机即可。(下图是选与不选出现的分支结果)
一、递归法
这是最容易理解的方式也是通用模板,但是时间复杂度和回溯法一样是O(n!)
class Solution
{
private:
int dp(vector<int>&nums,int step)//step 为当前数字的下标
{
/*模板出口*/
if(step>=nums.size())//递归出口 理解为当下标大于数组长度时停止递归
{
return 0;
}
/*模板 选和不选的问题*/
int choose = nums[step]+dp(nums,step+2);//选择当前数字,下一个数字就不再选择所以step+2
int nochoose = dp(nums,step+1);//不选择当前数字,下一个数字可以选择所以step+1
return max(choose,nochoose);//我们不用去关心过程是怎么样的 把选和不选的问题抛给计算机
//如果还是不明白的可以看一下上面的图
}
public:
int rob(vector<int>&nums){
return dp(nums,0);
}
};
运行代码试一下,emmm…当nums的长度少于30的时候还是挺顺畅的,但当nums数组的内容不断扩展的时候问题就来了,对于普通的电脑来说一般都是“超时”。
试想一下,递归的过程中对于每一次的结果都是从根节点出发的,即从
sum=0 -> sum=4 -> sum=0 -> sum=2 -> sum=0 …->sum=3
但是从上图中我们可以看出有一些节点可以用上一次的节点从发选择结果,
例如sum=0->sum=4的过程中,我们可不可以把sum=1这个结果保存下来然后直接进行下一轮选择呢。
二、数组法
利用数组保存已经遍历节点的结果(剪枝)
—唯有懂得怎么保存先前的结果才算真的懂得了动态规划
class Solution{
int rob(vector<int>& nums) {
//--------边界检查---
if(nums.size()==0)
return 0;
if(nums.size()==1)
return nums[0];
//----------------
vector<int>v(nums.size()+2);//申请一个nums.size()+2大小的数组
v[0]=0;v[1]=0;//假设nums[0]之前还有两个数字0,0
for(int i=2;i<=nums.size()+1;++i)//回到和方法一同样的问题 选和不选
{
int choose = nums[i-2]+v[i-2];//选择 跳跃一个结果选择
int nochoose = v[i-1];//不选 保存当前结果
v[i]=max(choose,nochoose);
//我们不用去关心过程是怎么样实现的 只需要把选和不选的问题抛给计算机即可
}
return v[nums.size()+1];
}
};
只遍历了一次数组,时间复杂度为O(n)