题目描述
给出一个长度为n的序列A1, A2,···, An, 求最大连续和。换句话说,要求找到1<=i<=j<=n, 使得Ai + Ai+1 + ···+ Aj 尽量大。
蛮力枚举
int MaxSubArray(int* A, int n)
{
int maxSum = a[0];
int currSum = 0;
for(int I = 0; I < n; I++)
{
for(int j = I; j < n; j++)
{
for(int k = I; k <= j; k++)
{
currSum += A[k];
}
if(currSum > maxSum)
maxSum = currSum;
//要记得清零,否则sum最终存放的是所有子数组的和
currSum = 0;
}
}
return maxSum;
}
此方法的时间复杂度为O(n^3)。
递推法
试着优化下这个算法。设 Si = A1 + A2 + ··· + Ai, 则 Ai + Ai+1 + ··· + Aj = Sj - Si-1。这个式子的用途相当广泛,其直观含义是“连续子序列之和等于两个前缀和之差”。有了这个结论,最内层的循环就可以忽略了。
S[0] = 0;
for(I = 1; I <= n; I++)
S[i] = S[i-1] + A[i]; //递推前缀和 S
for(I = 1; I <= n; I++)
for(j = I; j <= n; j++)
best >?= S[j] - S[I-1]; //更新最大值
“计算S”时间复杂度为O(n), 二重循环的时间复杂度O(n^2)。
分治法
- 划分问题: 把问题的实例划分成子问题。
- 递归求解: 递归解决子问题。
- 合并问题: 合并子问题的解得到原问题的解。
int maxsum(int* A, int x, int y) //返回数组在左闭右开区间[x,y)中的最大连续和
{
int i, m, v, L, R, max;
if(y - x == 1)
return A[x]; //只有一个元素,直接返回
m = x + (y-x)/2; //分治第一步:划分成[x,m) 和[m,y)
max = maxsum(A, x, m) >? maxsum(A, m ,y); //分治第二步:递归求解
v = 0;
L = A[m-1]; //分治第三步:合并(1)——从分界点开始往左的最大连续和L
for(I = m -1; I >= x; I--)
L >? v += A[I];
v = 0;
R = A[m]; //分治第三步:合并(2)——从分界点开始往右的最大连续和R
for(I = m; I < y; I ++)
R >? v += A[I];
return max >? (L+R); //把子问题的解与L和R比较
}
时间复杂度O(NlogN)
动态规划(beautiful)
令currSum是以当前元素结尾的最大连续子数组的和, maxSum是全局的最大子数组的和, 当往后扫描时, 对第 j 个元素有两种选择,要么放入前面找到的子数组, 要么作为新子数组的第一个元素:
- 如果 currSum > 0, 则令currSum + a[j];
- 如果currSum < 0, 则currSum 被置为当前元素,即currSum = a[j]
举例说明,a = {1, -2, 3, 10, -4, 7, 2, -5}.
currSum: 0 → 1 → -1 → 3 → 13 → 9 → 16 → 18 → 13
maxSum: 0 → 1 → 1 → 3 → 13 → 13 →16 → 18 → 18
int MaxSubArray(int* a, int n)
{
int currSum = 0;
int maxSum = a[0];
for(int j = 0; j< n; j++)
{
if(currSum > 0)
currSum += a[j];
else
currSum = a[j];
if(currSum > maxSum)
maxSum = currSum;
}
return maxSum;
}
从前往后扫描一遍数组,即完成求最大连续子数组和的需求,所以时间复杂度为 O(n)。