对于这种与什么什么子 有关的题,通常都可以使用动态规划来解,因为数组元素之间产生了联系
第一步定义数组含义
这里我选择定义一个二维数组dp[i][j]为i与j分别代表nums数组下标,dp[i][j]则代表在nums下标为i到下标为j之间的元素和
第二步找数组元素之间的关系式,这里的关系式也比较容易找
面对一个连续的子数组,如果要求dp[ i ][ j ]的话,我们直接联想到dp[ i ][ j-1 ](不要忘了我们定义的数组含义),dp[ i ][ j ]与dp[ i ][ j-1 ]之间只相差一个 nums[ j ]
所以我们的关系式就是 dp[i][j] = dp[i][j - 1] + nums[j]
第三步初始化
这里要注意的是数组越界问题,当j==0的时候如果代入关系式,就会产生数组越界问题
且当i==j 的时候,我们也要将数组元素赋值为nums[ j ],以便关系式正常使用
class Solution { public: int MAXN = -10001; int maxSubArray(vector<int>& nums) { int n = nums.size(),z=0; vector<vector<int> >dp(n);//定义二维数组dp[i][j]为i与j分别代表nums数组下标,dp[i][j]则代表在nums下标为i到下标为j之间的元素和 for (int i = 0; i < n ; i++) { dp[i].resize(n);//每行开nums.size()+1个元素 } while(z!=n) { dp[z][z]=nums[z];//初始化 if(nums[z]>MAXN)//避免nums只有一个元素时输出-10001 { MAXN=nums[z]; } z++; } for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (j<=i || j - 1 < 0)//避免数组越界,和保证j>i { continue; } dp[i][j] = dp[i][j - 1] + nums[j];//关系式 if (dp[i][j] > MAXN) { MAXN = dp[i][j];//找到所有子数组和中的最大值 } } } return MAXN; } };
好了代码写完了,提交一下~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
超时了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
我发现该代码在遇到接近该题最大数据的时候即超时53. 最大子数组和 - 力扣(LeetCode) (leetcode-cn.com)
是不是我们写错了呢,其实不然,只是我们写的代码效率没有达到该题的要求
以下题解来自LeetCode
我看了题解,发现人家的动态规划只使用了一维数组dp,看来动态数组第一步定义的不好也不行啊!!!
思路:
我们 不知道和最大的连续子数组一定会选哪一个数,那么我们可以求出 所有 经过输入数组的某一个数的连续子数组的最大和
因此我们的子问题就变成了求经过nums各个元素的连续子数组和
但是,很明显,我们发现貌似子问题之间的联系难以发现
例如:
经过 -3 的连续子数组的最大和是多少。
「经过 -3 的连续子数组」我们任意举出几个:
[-2,1,-3,4] ,-3是这个连续子数组的第 3 个元素;
[1,-3,4,-1] ,-3是这个连续子数组的第 2 个元素;我们发现,我们不确定的是:-3 是连续子数组的第几个元素
因此我们改变子问题为:求以nums各个元素结尾的连续子数组和
子问题 1:以 -2 结尾的连续子数组的最大和是多少;
以 -2 结尾的连续子数组是 [-2],因此最大和就是 -2。子问题 2:以 1 结尾的连续子数组的最大和是多少;
以 1 结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。−2+1=−1<1 ,因此「子问题 2」 的答案是 1。到这里,我们不难发现子问题的联系了
大家发现了吗,如果编号为 i 的子问题的结果是负数或者 0,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉,这是因为:
- 一个数 a 加上负数的结果比 a 更小;
- 一个数 a 加上 0 的结果不会比 a 更大;
而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 0,那么子问题 i + 1 的答案就是以 nums[i] 结尾的那个数
第二步找数组元素之间的关系式
代码实现如下:
class Solution { public: int MAXN = -10001; int maxSubArray(vector<int>& nums) { int n=nums.size(); vector<int>dp(n);//定义dp[i]为到下标为i的最大子数组和 dp[0]=nums[0];//初始化 MAXN=nums[0];//排除只有一个数字的情况 for(int i=1;i<n;i++) { dp[i]=max(dp[i-1]+nums[i],nums[i]);//关系式 if(dp[i]>MAXN)//找最大值 { MAXN=dp[i]; } } return MAXN; } };
到这里就结束了吗???
不,结合我们得到的关系式,我们发现dp[ i ]只与dp[ i-1 ]有关
你可以想到什么???没错就是滚动数组
class Solution { public: int maxSubArray(vector<int>& nums) { int pre = 0, maxAns = nums[0]; for (const auto &x: nums) { pre = max(pre + x, x); maxAns = max(maxAns, pre);//求最大值 } return maxAns; } };
下面分享另外一种做法(出自LeetCode)
该算法效率很高,不过较难想到吧~~
可以直接分析写出代码
- 假如全是负数,那就是找最大值即可,因为负数肯定越加越大。
- 如果有正数,则肯定从正数开始计算和,不然前面有负值,和肯定变小了,所以从正数开始。
- 当和小于零时,这个区间就告一段落了,然后从下一个正数重新开始计算(也就是又回到 2 了)。
class Solution { public: int maxSubArray(vector<int>& nums) { int ans = -1;//初始化 int max = -INT_MAX; if(nums.size()==1) return nums[0];//nums只有一个元素的单独考虑 for(int i = 0;i<nums.size();i++){ if(ans<0)//如果子数组和 <0 了,相当于舍弃这个区间 ans=0; ans = ans + nums[i]; if(ans>max)//更新最大值 max= ans; } return max; } };