子数组最大累加和dp问题I(保姆级!)
1.前言
之前写的博客都是站在已经解出题目的角度,有些分析过程很短,而且比较晦涩。
这篇站在做题者的角度,一步一步分析,一步一步讲解
2.连续子数组的最大和
1.一个常识
题目要求一个最大的连续子数组和
因为每一个子数组必然有一个结束的地方,结束的地方一定是0到nums.size()-1之间的一个
所以只需要分别求出以每一个位置结尾的子数组的最大和,再选出最大的就行了
dp[i]:以i位置为结尾的子数组(必须含有i )最大和,下面推断递推式:
2.用一个例子自行感受一下(感受不出可以边看“3”边感受)
nums = {1,2,3,-4,-9,1}
索引 = 0,1,2,3, 4, 5
直观来看,dp[0] = 1,dp[1] = 2 + 1 = 3,dp[2] = 3 + 2 + 1 = 6;
dp[3] = -4 + dp[2] = 6,dp[4] = -9 + dp[3] = -6,dp[5] = 1;
3.具体分析递推公式
以i位置为结尾的子数组最大和,就是两种情况,要么选择1)往前囊括,要么2)孤寡一人。
1)如果往前囊括,势必要把i-1位置的数囊括进来,那囊括的东西总共就是nums[i] + nums[i-1] + ...
nums[i]是确定的,只需nums[i-1] + ...取得最大值,这不就是dp[i-1]吗?
2)如果孤寡一人,就是nums[i]
取1)和2)之中的较大值,就是dp[i]
因此:dp[i] = max(nums[i],nums[i] + dp[i-1]);
4.注意初始状况
这里的边界指的是得单独确定的地方,就是dp[0],它要直接给出,后面的dp[1]的递推式中要用到dp[0],然后dp[2]用dp[1],以此类推。最后所有的dp值才能顺次推出。
dp[0] :表示的就是以0为结尾的子数组,所以dp[0] = nums[0]
5.代码详解
class Solution {
public:int maxSubArray(vector<int>& nums) {int dp[100007];dp[0] = nums[0];int maxNum = nums[0];for(int i = 1;i < nums.size();i ++){dp[i] = max(nums[i],dp[i-1]+nums[i]);maxNum = max(maxNum,dp[i]);}return maxNum ;}
};
dp数组定义100007,因为题目里面说了nums.length最大10的5次方
然后dp[0] 赋值为nums[0]
maxSum用于记录遍历dp时的最大值,初始值设为dp[0],也就是nums[0]
循环遍历nums数组,利用递推式更新dp数组。然后看看当前dp[i]是否大于maxSum,如果大于,更新maxSum为当前dp[i]
返回maxSum
6.空间优化:我们发现,dp[i]的计算仅仅依赖dp[i-1],
若是我们设置一个变量,int cur = dp[0],再计算dp[1]的时候,只要有cur就行了,计算出dp[1]之后,只需要把dp[1]赋值给cur,然后就能计算出dp[2],.......
我们通过一个变量的“滚动”代替了一个数组!
int cur = nums[0];int maxNum = nums[0];for(int i = 1;i < nums.size();i ++){cur = max(nums[i],cur + nums[i]);maxNum = max(maxNum,cur);}return maxNum;
7.这道题还有别的方法,会在之后的“归并分治”博客中讲解
3.不能取相邻元素的子序列的最大值
1.沿袭
我们还是可以把问题转为求各个以i做结尾(子序列必须包含i)的子序列,然后选择最大的即可
2.递推公式
dp[i] = max(dp[i-2]+nums[i],dp[i-3]+nums[i])
由于取得元素不能相邻,如果取了当前位置i的元素,必然不能取i-1。
1)如果取了i-2,那么问题就转化成了求以i-2做结尾的最大值+nums[i],即dp[i-2]+nums[i]
2) 如果取了不取i - 2,取 i -3,同理,问题转化为dp[i-3]+nums[i]
3)还有情况吗?如果不取i-2,i-3,而是取i-4行吗?
取i-4,i-4和i之间的i-2也能取。而且必然是算上i-2会“更好一些”,因为题目规定nums中的元素都是非负数!
因此3)不如1)优秀,我们要比较最大值,肯定舍弃3)!
再往下,以此类推,要么不如1),要么不如2)!
“不严谨的”证明了递推式的正确性,其实动态规划有时候就是猜出来的。。。
3.边界情况
考虑到递推式中涉及i-3,所以至少得把dp[0],dp[1],dp[2],dp[3]列出
dp[0] = nums[0];
dp[1] = nums[1];//由于不能取相邻的,dp[1]只能取nums[1]
dp[2] = max(nums[2],nums[2]+nums[0])
dp[3] = max(nums[3],nums[3]+nums[1],nums[3]+nums[0]//注意这里一定不要忘了可以连续跳两个,选择0和3,不要少了第三项!!
4.代码
class Solution {public:int rob(vector<int>& nums) {int dp[102];int maxSub = nums[0];dp[0] = nums[0];if(nums.size() >= 2)dp[1] = nums[1];maxSub = max(maxSub,dp[1]); if(nums.size() >= 3)dp[2] = nums[0] + nums[2];maxSub = max(maxSub,dp[2]);if(nums.size() >= 4)dp[3] = max(nums[0] + nums[3],nums[1] + nums[3]);maxSub = max(maxSub,dp[3]);for(int i = 4;i < nums.size();i ++){dp[i] = max(dp[i-2] + nums[i],dp[i-3] + nums[i]);maxSub = max(maxSub,dp[i]);}return maxSub;}};
列出所有边界情况然后执行循环就行,和上面那题差不多
需要注意的是,dp2和dp3计算时没有和nums[2],nums[3]比较,还是因为题目中说数组中全是非负数,因此nums[0] + nums[2] >= nums[2],nums[1]+nums[3] >= nums[3]。
这道题同样可以不开数组dp,选择使用三个变量pre3,pre2,pre1来记录dp[i-3],dp[i-2],dp[i-1]
你不妨试试!