给定n个整数(可能有负)的序列,求最大子段和
例如序列(-20,11,-4,13,-5,-2)最大子段和为20(子段为{11,-4,13})
思路:
这道题目的难点在于子段起点和终点不确定:想想吧,如果题目规定必须包含a[0],那还不好求吗?
只要设置两个变量a,b,a负责记录前最大值,b负责从a[0]累加到a[n],如果b>a,则令a=b,只需扫描一趟就能求解出来。
所以可以想到,在子段任意固定一点,以此为起点向序列左右两端探索最大值,然后将这些值加起来不就行了吗?
当然出于效率的考虑,定点选取的肯定是序列中心
序列 a0 a1 a2 a3 ... aj ... an-2 an-1 an
↑
←-- 向左探索(最大值s1) 定点 向右探索(最大值s2)--→
其中j=(0+n)/2
最大序列和=s1+s2
求s1:
int s1,left;
for(int i=j;i>=0;--i){
left+=a[i];
if(left>s1) left=s1;
}
求s2:
int s2,right;
for(int i=j+1;i<=right;++i){
right+=a[i];
if(right>s2) right=s2;
}
但是也有一个很明显的问题:如果最大子序在a[j]的右边或左边呢?
比如最大子序在a[j]左边,即a[0]~a[j-1]。但是按照上面的算法,a[j]无论如何都被加进去了,所以肯定是算不对的,那怎么办呢?
没办法,我们得把左(a[0]~a[j-1])的最大子序和:leftMax和右(a[j+1]~a[n])的最大子序和:rightMax给算出来,然后比较leftMax,rightMax,(s1+s2),看看哪个大。
怎么算leftMax以及rightMax呢?分而自治:
我们将a[0]~a[j-1]再次分为左中右三块,对其左中右再分为左中右三块。。。往复下去,这些序列迟早变成长度为1的单个序列,对于这些序列,它们的最大子序列很明显就是它们自己啦
例: 求序列 -20,11,-4,13,-5,-2的最大子序和 Max
←--s1 | s2--→
序列 -20 11 -4 13 -5 -2
1、由上面的分析,很容易算出 S1+S2=20
由于a[0]~a[n/2](左) 或 a[n/2+1]~a[n](右) 可能会出现最大值,所以将序列划分为
{-20,11}(左)和{13,-5,-2}(右),并分别求其最大子序和 MaxLef 及 MaxRig
2、对于{-20,11},由于只有两个元素,所以很明显:
max(左子序和)=-20,max(右子序和)=11,s1+s2=11;
∴MaxLef = max( max(左子序和) , s1+s2 , max(右子序和) ) = max(-20,11,11)=11
3、对于{13,-5,-2}:
s1+s2=8 由于其左右子序列均只有一个元素,
所以max(左子序和)=左子序列=13,max(右子序和)=右子序列=-2
∴MaxRig = max( max(左子序和) , s1+s2 , max(右子序和) ) = max(13,8,-2)=13
4、求得了S1+S2,MaxLef,MaxRig,
那么对于序列 -20,11,-4,13,-5,-2的最大子序和很自然能求出来:
Max = max ( MaxLef,S1+S2,MaxRig ) = max(11,20,13) = 20
代码:
#include<stdio.h>
#include<stdlib.h>
int MaxSum(int a[],int left,int right){
int sum=0,midSum=0,leftSum=0,rightSum=0;
int center,s1,s2,lefts,rights;
if(left==right) {sum=a[left];}
else{
center=(left+right)/2;
leftSum=MaxSum(a,left,center);
rightSum=MaxSum(a,center+1,right);
s1=0,lefts=0;
for(int i=center;i>=left;i--){
lefts+=a[i];
if(lefts>s1) s1=lefts;
}
s2=0;rights=0;
for(int j=center+1;j<=right;j++){
rights+=a[j];
if(rights>s2) s2=rights;
}
midSum=s1+s2;
if(midSum<leftSum) sum=leftSum;
else sum=midSum;
if(sum<rightSum) sum=rightSum;
printf("\n\n序列:");
for(int k=left;k<=right;++k){
printf("%d ",a[k]);
}
printf("\n");
printf("左子序列:");
for(int i2=left;i2<=center;++i2){
printf("%d ",a[i2]);
}
printf(" 最大值%d\n",leftSum);
printf("右子序列:");
for(int i3=center+1;i3<=right;++i3){
printf("%d ",a[i3]);
}
printf(" 最大值%d\n",rightSum);
printf("中序列:");
for(int i4=left;i4<=right;++i4){
printf("%d ",a[i4]);
}
printf("s1=%d,s2=%d\n",s1,s2);
}
return sum;
}
int main(){
int a[6]={-20,11,-4,13,-5,-2};
printf("全序列:\n");
for(int i=0;i<5;++i){
printf("%d ",a[i]);
}
printf("\n");
printf("\n最大值:%d\n",MaxSum(a,0,5));
return 0;
}