问题:
已知 一个整数序列a[n]:n个整数(有正负)
求 在这个序列的所有子序列(连续)中,最大的和是多少?
注意:
以下代码都要求最大和 【可为负数】,若要求最大和 【非负】,只需最后判断一下即可
一、贪心思想
假设a[l]...a[r]的和为 sum
如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留
如果 sum < 0,则说明 sum 对结果无增益效果,需要舍弃
明白了这个道理,那么对于第i个数来说,我们查看他之前的sum,如果sum<0,那么当前的最大连续子序列和就是它本身(舍弃前面的);如果sum>0,那么当前的最大和就是sum+a[i] (sum保留了)
sum=0的情况都保留和丢弃都可以
int ans=a[1]; //ans初始化为第一个元素,不能是0,因为最大值可能是负数
int sum=0;
for (int i=1; i<=n; i++){
if(sum>=0) sum+=a[i];
else sum=a[i];
ans=max(ans,sum);
}
二、动态规划
用f[i]表示以第i个数a[i]结尾的最大子序列和。
那么f[i]的状态转移方程也很好得出,对于a[i]是单独算还是加入前面的f[i-1],即:f[i]=max(f[i-1]+a[i],a[i])
问:为什么会想到用f[i]表示以第i个数a[i]结尾的最大子序列和?
连续的子序列的和,这是关键,只有f[i]包含了a[i],递推才可以进行下去,不然就不连续了。
代码:用ans代替f[i]的迭代,省空间。
int ans=a[1],sum=0;
for (int i=1; i<=n; i++){
sum=max(sum+a[i],a[i]);
ans=max(ans,sum);
}
三、分治思想≈线段树
对于一个区间 [l, r],我们可以维护四个量:
lSum 表示 [l, r] 内以 l 为左端点的最大子段和
rSum 表示 [l, r] 内以 r 为右端点的最大子段和
mSum 表示 [l, r] 内的最大子段和
iSum 表示 [l, r] 的区间和
然后转移条件,相当于线段树:
首先最好维护的是 iSum,区间 [l, r] 的 iSum 就等于「左子区间」的 iSum 加上「右子区间」的 iSum。
对于 [l, r] 的 lSum,存在两种可能,它要么等于「左子区间」的 lSum,要么等于「左子区间」的 iSum 加上「右子区间」的 lSum,二者取大。
对于 [l, r] 的 rSum,同理,它要么等于「右子区间」的 rSum,要么等于「右子区间」的 iSum 加上「左子区间」的 rSum,二者取大。
当计算好上面的三个量之后,就很好计算 [l,r] 的 mSum 了。我们可以考虑 [l,r] 的 mSum 对应的区间是否跨越 m——它可能不跨越 m,也就是说 [l,r] 的 mSum 可能是「左子区间」的 mSum 和 「右子区间」的 mSum 中的一个;它也可能跨越 m,可能是「左子区间」的 rSum 和 「右子区间」的 lSum 求和。三者取大。
PS:线段树的学习和使用:【未完成】线段树的学习和使用_马小超i的博客-CSDN博客_线段树的使用
class Solution {
public:
struct Status {
int lSum, rSum, mSum, iSum;
};
Status pushUp(Status l, Status r) {
int iSum = l.iSum + r.iSum;
int lSum = max(l.lSum, l.iSum + r.lSum);
int rSum = max(r.rSum, r.iSum + l.rSum);
int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum);
return (Status) {lSum, rSum, mSum, iSum};
};
Status get(vector<int> &a, int l, int r) {
if (l == r) return (Status) {a[l], a[l], a[l], a[l]};
int m = (l + r) >> 1;
Status lSub = get(a, l, m);
Status rSub = get(a, m + 1, r);
return pushUp(lSub, rSub);
}
int maxSubArray(vector<int>& nums) {
return get(nums, 0, nums.size() - 1).mSum;
}
};
并非原创!