求数组的子数组之和的最大值。
解法1:分治法,将所给数组A[0],…A[n-1]分为长度相等的两段数组A[0],…,A[n/2-1]和A[n/2],…,A[n-1],分别求出这两段数组各自的最大子段和,则原数组的最大子段和为以下三种情况的最大值:
1. A[0],…A[n-1]的最大子段和与A[0],…,A[n/2-1]的最大子段和相同;
2. A[0],…A[n-1]的最大子段和与A[n/2],…,A[n-1]的最大子段和相同;
3. A[0],…A[n-1]的最大子段跨过其中间两个元素A[n/2-1]和A[n/2]。
第1和第2两种情况是问题规模减半的相同子问题,可以通过递归求得。
第3种情况只要找到以A[n/2-1]结尾的和最大的一段数组之和S1,以及以A[n/2]开始和最大的一段数组之和S2,那么第3种情况的最大值为S1+S2,只要对原数组进行一次遍历即可。
分治法使得问题被分解为两个问题规模减半的子问题再加上一次遍历算法。总的时间复杂度为O(N*logN)。
解法2:动态规划法,考虑数组的第一个元素A[0],以及最大的一段数组A[i],…,A[j]跟A[0]之间的关系,有以下几种情况:
1.当0=i=j时,元素A[0]本身构成和最大的一段;
2.当0=i<j时,和最大的一段以A[0]开始;
3.当0<i时,元素A[0]跟和最大的一段没有关系。
这样可以将一个大问题(N)转化为一个较小的问题(N-1)。假设已经知道A[1],…,A[N-1]中和最大的一段数组之和为All[1],并且已经知道A[1],…,A[N-1]中包含A[1]的和最大的一段数组为Start[1]。则由以上三种分析的情况可看出A[0],…,A[N-1]中问题的解All[0]是三种情况的最大值max{A[0],A[0]+Start[1],All[1]}。通过这样的分析可以看出这个问题无后效性,可以使用动态规划的方法解决。代码如下:
int MaxSum(int* A, int n)
{
Start[n-1] = A[n-1];
All[n-1] = A[n-1];
for(i = n-2; i>=0; i--) //从数组末尾往前遍历,直到数组首
{
Start[i] = max( A[i], A[i]+Start[i+1]);
All[i] = max(Start[i],All[i+1]);
}
return All[0]; //遍历完数组,All[0]中存放结果
}
时间复杂度为O(N);
改进:Start[i] = max( A[i], A[i]+Start[i+1]);
All[i] = max(Start[i],All[i+1]);
如果Start[i+1]<0,则Start[i]=A[i]。并且在这两个递推式中,其实都只需用两个变量就可以了。Start[k+1]只有在计算Start[k]时使用,而All[k+1]也只有在计算All[k]时使用。所以改进程序只需O(1)的空间就足够了:
int MaxSum(int* A, int n)
{
nStart = A[n-1];
nAll= A[n-1];
for(i = n-2; i>=0; i--)
{
nStart = max( A[i], A[i]+nStart);
nAll = max(nStart,nAll);
}
return nAll;
}