最大子序列和问题
最优的算法逻辑很简单,扫描,如果和>0就继续扫描,刷新一次最大和,如果<0就置0重新累积和:
int Sum=0;
int MaxSum;
for (int i = 0; i < array.size(); ++i)
{
if(Sum<=0)
Sum=array[i];
else
Sum+=array[i];
if(Sum>MaxSum)
MaxSum=Sum;
}
return MaxSum;
它基于这样一个事实:假设A[i...j]是最大和子序列,暂且叫L,则
1)其左侧和右侧(A[i-1],A[j+1],如果存在的话)定然是一个负数,并且其左侧任何一个带A[i-1]或其右侧任何一个带A[j+1]的连续子序列的和必然<=0。
(证明很容易,如果不成立则一定加入了最大和子序列中了)
2)A[i] 和 A[j]一定是正数,不然肯定被舍弃了。
3)再进一步,从最大子序列的左侧往右侧看,L任意从A[i]开始的连续子序列和必然都是正数,如果考虑到A[i]也是正数,则可以这么说,将L随机“劈”成两半,左侧的和必然>0,不然
左侧和+右侧和 = 最大子序列和
若左侧和<0,则右侧和>最大子序列和,那最大子序列就是右侧部分了,没必要加上左侧这个累赘。
其实右侧也一样。这样一来,随机劈两半,两半必>0。
有了上面的积累,我们可以将最大和子序列比作海上的“孤岛”,虽然孤岛上可能还有小水潭,但是总体上是“正的”,任意劈成两半,两半的“和”也是正的(土可以填住水潭)。而孤岛两侧都是“大海”,可能偶尔有一两个“峰顶”,但是不足以填满和孤岛之间的海域。
(其实就是上述程序中累加和Sum和i轴围成的图形,我也不知道为什么要说得这么复杂。。)
为了找到海上最大的这么一座岛,我们可以这么干,坐着摩托艇从起点出发,如果遇到能填住海水的小岛,我们就拿出照相机拍照刷出来,和上一次拍的小岛比较一下,如果比以前岛要大,就把以前照片扔了,留下这一张(泳裤口袋太小只能装一张照片)。
这样只要泥土还是多于海水就一直拍照比较一下,一旦发现海水多于泥土了就重新寻找。
这样我们一定可以越过只散布着零零星星泥土的海域,成功找到最大的岛。(如果只从寻找最大岛这一目的,其实之前的路程都是为了赶路,找到坑坑洼洼之后的大岛)
但是,对于上面的算法,我们只能做到将最大岛的风景“拍”下来,然后继续前进,因为在到达终点之前我们是不知道这里就是最大的岛,要继续往前填海填土试探,如果海水淹没了所有土地,则计零重新开始。实际上我们之前越过零星泥土的海域都是这么干的,当时我们也将当时的泥土认为是最大的岛,并拍下岛的风景,当遇到海水淹没再前进,遇到更大的岛再拍新的风景(照相机只能保存一张图片)。
所以,明显也能看出上述的算法的缺点是,到达终点时,只能留下最大岛的风景,却不知岛的位置。即知道最大子序列的和值是多少,却不知道最大和子序列是什么。
如果现在换一个问题:求一个序列的最大子序列。
则很明显,要加两个笔记本(笔记本很特殊,只能记录一个数字),每次重新记录,看到小岛时,记下将当前小岛位置记录为“最大岛的起点和终点”,如果往后发现岛在变大(岛后面原来有更大的岛),就将终点更新,否则(变小或者直接被海水淹没)终点不变。
talk is cheap,show codes:
int Sum = 0; // 照相机
int MaxSum; // 口袋里的照片
int start,final; // 两个笔记本
for (int i = 0; i < array.size(); ++i)
{
if(Sum<=0)
{
Sum = array[i];
start = i;
final = i;
}
else
Sum+=array[i];
if(Sum>MaxSum)
{
MaxSum=Sum;
final = i; // 重新记录小岛的终点
}
}
return MaxSum,start,final;