思路一:分治
1.对于序列a0…
a
n
a_n
an,要求整个序列的最大连续子序列和,我们可以采用分治的思想考虑,将序列较为平均的分成两部分,a0…
a
n
/
2
a_{n/2}
an/2,
a
n
/
2
+
1
a_{n/2+1}
an/2+1…
a
n
a_n
an,那么要求的连续最大子序列和可能是前半部分的最大子序列和leftmax、后半部分的最大子序列和rightmax以及跨越两段的最大连续子序列和middlemax。
2.我们对序列进行一般化,记要求序列为al…
a
r
a_r
ar对于跨越两段最大的子序列middlemax有一个特征,记m=(l+r)/2,那么该子序列的左半部分应该是以下标m结尾的连续子序列ai…
a
m
a_m
am(i>=l),右半部分应该是以下标m+1开始的连续子序列
a
m
+
1
.
.
.
.
.
.
.
a
j
a_{m+1}.......a_j
am+1.......aj(j<=r)。
3.该问题可以通过分治求解子问题leftmax和rightmax,然后合并过程中再计算middlemax,即可递归获得结果。
4.难点是如何求取middlemax:假设我们已经能够求取出正确的leftmax和rightmax,在计算完leftmax和rightmax后,仅仅是知道leftmax和rightmax是不足以求取middlemax的,因此我们需要在求解leftmax和rightmax过程中返回一些信息便于我们求解middlemax,middlemax=(left中以下标m结尾的最大子序列和)+(right中以下标m+1开头的最大子序列和)。
5.作为递归程序,在利用利用子程序返回信息的同时,程序本身也应该计算返回相同结构的信息供其父程序使用,因为我们求解middlemax需要使用到(left中以下标m结尾的最大子序列和)+(right中以下标m+1开头的最大子序列和),所以我们也需要返回本身以r结尾的最大子序列rsum和以及以l开头的最大子序列和lsum。同时还有本序列求解的最大子序列和msum。
6.我们来看这三个返回结果是否足以供当前父程序向上返回序列{left,max}的rsum、lsum和msum,显然,在求解lsum和rsum出现了问题,我们还需要知道left的序列总和ltsum以及right的序列总和rtsum,以求解当前父程序的lsum和rsum,相应的,当前父程序也需要返回当前求解序列的总和tsum。
7.综上,计算每个子问题都需要返回
以r结尾的最大子序列rsum、
以l开头的最大子序列和lsum、
求解序列的总和tsum
求解的最大子序列和msum
将其设计组合成一个结构体rdata
8. rdata.lsum=max(leftdata.lsum,leftdata.tsum+rightdata.lsum);
rdata.rsum=max(rightdata.tsum+leftdata.rsum,rightdata.rsum);
rdata.tsum=leftdata.tsum+rightdata.tsum;
rdata.msum=max(max(leftdata.msum,rightdata.msum),leftdata.rsum+rightdata.lsum);
9.最特殊情况l=r,rdata的四个值应该都为nums[l]
代码
class Solution {
public:
struct data
{
int lsum;//以l作为左边界的最大值
int rsum;//以r作为右边界的最大值
int tsum;//区间总和
int msum;//区间最大值
};
struct data getmax(vector<int>& nums,int l,int r)
{
struct data rdata;
if(l==r)
{
rdata.lsum=nums[l];
rdata.rsum=nums[l];
rdata.tsum=nums[l];
rdata.msum=nums[l];
return rdata;
}
else
{
int m=(l+r)/2;
struct data leftdata =getmax(nums,l,m);
struct data rightdata=getmax(nums,m+1,r);
rdata.lsum=max(leftdata.lsum,leftdata.tsum+rightdata.lsum);
rdata.rsum=max(rightdata.tsum+leftdata.rsum,rightdata.rsum);
rdata.tsum=leftdata.tsum+rightdata.tsum;
rdata.msum=max(max(leftdata.msum,rightdata.msum),leftdata.rsum+rightdata.lsum);
return rdata;
}
}
int maxSubArray(vector<int>& nums) {
return getmax(nums,0,nums.size()-1).msum;
}
};
思路二:贪心算法
1.假设最优序列为
a
l
a_l
al…
a
r
a_r
ar,那么任取一个子序列
a
l
a_l
al…
a
i
a_i
ai(l<=i<=r),该子序列和应该大于0,否则该子序列只会导致
a
l
a_l
al…
a
r
a_r
ar总和变小。
2.我们从第一个元素开始遍历,维护一个希望序列和total,total是指一段序列
a
t
1
a_{t1}
at1…
a
t
2
a_{t2}
at2的和,其任意一段子序列
a
t
1
a_{t1}
at1…
a
i
a_{i}
ai(l<=i<=t2)的和
t
o
t
a
l
i
total_i
totali是大于0的。
以及维护一个最优解ans,ans初始值置为负无穷。
3.对于每一个a[i],如果大于0,则直接加入total当中;如果小于0,则需要考虑其加入是否会导致total小于等于0,如果是,则清空,total置为0;如果大于0,则判断更新ans的值:ans=max{ans,total+a[i],a[i]},以及total值的更新:total=total+a[i]。
4.如果序列全为负数或0,那么ans应该是最大的负数或0。
代码:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int ans=nums[0];
int total=nums[0]>0?nums[0]:0;
for(int i=1;i<nums.size();i++)
{
ans=nums[i]>ans?nums[i]:ans;
if(nums[i]>0)
{
total+=nums[i];
ans=total>ans?total:ans;
}
else if(nums[i]<0)
{
if(total+nums[i]<0)
{
total=0;
}
else
{
total+=nums[i];
}
}
}
return ans;
}
};