问题描述:
给定一个整数序列A[0],A[1], … , A[N-1],每个元素可正可负,求该序列中最大的连续和。
方法1 暴力求解法
这种方法最直接,暴力枚举每一个每一个连续片段的起点和终点,然后求相应
的和。其复杂度即为
O(n3)
。
算法描述为:
int maxx = A[0];
for(int i=0; i<N; i++)
for(int j=i; j<N; j++)
{
int sum = 0;
for(int k=i; k<=j; k++) sum+=A[k];
maxx = sum>maxx ? sum:maxx;
}
方法2 利用递推前缀和记录 0−i 项的和
这种方法使用了已经计算的信息,减少了重复工作,使得复杂度降为
O(n2)
。
算法描述为:
S[0] = A[0];
maxx= A[0];
for(int i=1; i<N; i++) S[i] = S[i-1]+A[i];
for(int i=1; i<N; i++)
for(int j=i; j<N; j++)
maxx = maxx>S[j]-S[i-1] ? maxx:S[j]-S[i-1];
方法3 采用二分法进行递归求解,进一步降低复杂度
这种方法可以有效降低复杂度,其复杂度为
O(nlongn)
。
代码如下:
// O(nlogn)复杂度
int maxsum(vector<int>& A, int x, int y)
{
if(y-x==1) return A[x];
int m = x+(y-x)/2;
int mx = maxsum(A, x, m);
int my = maxsum(A, m, y);
int maxx = mx>my ? mx:my;
int v = 0, L = A[m-1], R = A[m];
/// 从分界点开始往左的最大连续和L,并保证L不减
for(int i=m-1; i>=x; i--) L = L>=(v+=A[i]) ? L:v;
v = 0;
/// 从接点点开始往右的最大连续和R,并保证R不减
for(int i=m; i<y; i++) R = R>=(v+=A[i]) ? R:v;
return maxx>(L+R) ? maxx:L+R;
}
方法4 优化方法2,并使复杂度变为线性
这种方法同样利用了前缀和,并且用mx记录前i项中最大的连续和,在一次遍历A的过程中同时更新mx和sum,从而使得复杂度
O(n)
。
代码如下:
// O(n)复杂度
int maxsum2(vector<int>& A)
{
int * sum = new int[A.size()];
int mx = A[0]; // or mx = INT_MIN;
sum[0] = A[0];
for(int i=1; i<A.size(); i++) mx = mx>(sum[i]=sum[i-1]>0?sum[i-1]+A[i]:A[i]) ? mx:sum[i];
delete sum;
return mx;
}
这4种方法中以最后两种比较好用,而最后一种在一次遍历中同时更新了多个量,达到了算法的最优。这启发我们,在算法设计过程中,尽量充分利用已经计算的信息可以使得算法更加优化。
后记:
本质上第4种方法是一种DP。
其中sum[i]用来记录a[0]…a[i]的最大连续和。即
sum[0] = a[0]; sum[i] = max(a[i], a[i]+sum[i-1]);
如果sum[i-1]为负数,那么sum[i]=a[i],否则sum[i]=sum[i-1]+a[i];