53. 最大子序和(C++)
题目
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
分析与题解
暴力循环
比较容易想到的是暴力解题,即穷举所有的子区间:
- 使用双层循环,穷举所有的子区间;
- 然后再对子区间内的所有元素求和;
- 时间复杂度是立方级别
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int max = INT_MIN;
for(int i=0;i<n;i++){
int sum=0;
for(int j=i;j<n;j++){
sum += nums[j];
if(sum > max)
max = sum;
}
}
return max;
}
};
需要注意边界的问题:首先在外层循环遍历数组,作为求非连续数列的起点,然后从此出发逐个元素相加,每添加一个新的元素,sum值进行变化都需要与最终的结果进行比较,取较大值,最终一直添加到元素末位。
动态规划(状态转移方程1)
将问题切分为最优子问题进行求解。
①定义状态
我们使用一个数组或者哈希表dp存储子问题的答案。例如dp[i]
代表以nums[i]
结尾的连续子数组的最大和。
②思考转移状态方程
根据状态的定义,由于 nums[i]
一定会被选取。并且 dp[i]
所表示的连续子序列与 dp[i - 1]
所表示的连续子序列(有可能)就差一个 nums[i]
。
先考虑最简单的情况,假设数组 nums 全是正数,那么一定有 dp[i] = dp[i - 1] + nums[i]
。
但在一般情况下 dp[i - 1] 有可能是负数,例如前几个数都是负数,突然来了一个正数。于是分类讨论:
- 如果
dp[i - 1] >= 0
,那么可以把nums[i]
直接接在dp[i - 1]
表示的那个数组的后面。 - 如果
dp[i - 1] < 0
,那么加上前面的数反而越来越小了,不如以nums[i]
为起点作为dp[i]
数值。
状态转移方程即为:
\[d p[i]=\left\{\begin{array}{ll}
d p[i-1]+n u m s[i], & \text { if } \quad d p[i-1] \geq 0 \\
\text { nums }[i], & \text { if } \quad d p[i-1]<0
\end{array}\right.\]
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int sum = 0;
int result = INT_MIN;
for(int i=0;i<n;i++){
sum+=nums[i];
result = max(result, sum);
if(sum < 0)
sum = 0;
}
return result;
}
};
自底向上(状态转移方程2)
对于dp[i-1]
的讨论,最终无非是求子序列和的最大值,因此状态转移方程可以简写为:
\[d p[i]=\max \{n u m s[i], d p[i-1]+n u m s[i]\}
\]
因此我们不再讨论dp[i-1]的正负,直接将是/否添加dp[i-1]的值比较并取较大值。代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
if(n==0) return 0;
vector<int> dp(n);
dp[0] = nums[0];
int result = dp[0];
for(int i=1;i<n;i++){
dp[i] = max(dp[i-1]+nums[i], nums[i]);
result = max(result, dp[i]);
}
return result;
}
};
需要注意的是状态dp代表的是以nums[i]结尾的子序列和的最大值,但是对于所给序列的最大值并不一定以包含末尾元素,所以需要设置额外的变量来进行记录和比较。