问题描述:一个有N个整数元素的一维数组(A[0]...A[n-1]),这个数组当然有很多子数组,那么子数组之和的最大值是多少呢?
思路一:最直接能想到的方法就是枚举,暴力解法,复杂度为O(N^2)。
思路二:递归解法,将数组分成长度相等的两段数组,分别为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]。
这其实是一种分治算法,复杂度为O(nlogn)
思路三:动态规划思想,从A[n-1]算起到A[i]时候,最大的和在{A[i],nAll,nStart}(nAll表示A[n-1]到A[i+1]的子数组最大值,nStart表示含A[i]的子数组的最大值)中,故可以用动态规划的
方法解决。
思路四:这种方法从动态规划的思想想到的,很巧妙,从数组一直累加求最大值,如果和大于0,则可以一直累加,如果和小于0了,就直接舍弃掉前面重新再求和。
详见代码如下:
#include <algorithm>
#include <iostream>
#include <stdlib.h>
using namespace std;
int maxSum_1(int *A, int n) // O(n^2),暴力解法
{
int maximum = -100000000;
int sum;
for(int i = 0; i < n; i++)
{
sum = 0;
for(int j = i; j < n; j++)
{
sum += A[j];
if(sum > maximum)
maximum = sum;
}
}
return maximum;
}
int maxSum_2(int * A, int low, int high) // O(nlogn),递归
{
if(low == high)
return A[low];
int mid = low + ((high - low) >> 1);
int maxLeftSum = maxSum_2(A, low, mid);
int maxRightSum = maxSum_2(A, mid + 1, high);
// find the maximum sum of a sequnce crossing the midpoint
int maxLS, curLS;
maxLS = curLS = A[mid];
/* low <= mid */
for (int i = mid - 1; i >= low; --i)
{
curLS += A[i];
if (curLS > maxLS)
{
maxLS = curLS;
}
}
int maxRS, curRS;
maxRS = curRS = A[mid+1];
/* mid+1 <= high */
for (int j = mid + 2; j <= high; ++j)
{
curRS += A[j];
if (curRS > maxRS)
{
maxRS = curRS;
}
}
int maxMidSum = maxLS + maxRS;
return max(max(maxLeftSum, maxRightSum), maxMidSum);
}
int maxSum_3(int *A, int n) // O(n),最简单
{
int nStart = A[n-1];
int nAll = A[n-1];
for(int i = n - 2; i >= 0; i--)
{
nStart = max(A[i], nStart + A[i]);
nAll = max(nStart, nAll);
}
return nAll;
}
//O(n),从A[n-1]算起到A[i]时候,最大的和在{A[i],nAll,nStart}(nAll表示A[n-1]到A[i+1]的子数组最大值,nStart表示含A[i]的子数组的最大值)中,故可以用动态规划的方法解决。
int maxSum_4(int * A, int n)
{
int sum = 0;
int max = A[0];
for(int i = 0;i < n; i++)
{
if(sum > 0)
sum += A[i];
else
sum = A[i];
if(sum > max)
max = sum;
}
return max;
}
int main()
{
int A[] = {-2,5,3,-6,4,-8,6};
cout << maxSum_1(A, sizeof(A)/sizeof(A[0])) << endl;
cout << maxSum_2(A, 0,sizeof(A)/sizeof(A[0])) << endl;
cout << maxSum_3(A, sizeof(A)/sizeof(A[0]))<< endl;
cout << maxSum_4(A, sizeof(A)/sizeof(A[0]))<< endl;
system("pause");
}
扩展思考题:
1.如果数组首尾相连,请问使其和最大,怎么办?
2.如果题目要求同时返回最大数组的位置,算法应该如何改变,还能保持O(n)的时间复杂度么?
解:对于第一题,明天再写了,据说编程之美上的解答好像是有问题的,先附上链接http://www.ahathinking.com/archives/120.html#comments
第二题的话,运用方法4,当max变化时候,可以记录前后下标值,附上代码如下:
int maxSum_4(int * A, int n, int& first, int& second)
{
int sum = 0;
int max = A[0];
int cur = first = 0;
for(int i = 0;i < n; i++)
{
if(sum > 0)
sum += A[i];
else
{
sum = A[i];
cur = i;
}
if(sum > max)
{
first = cur;
max = sum;
second = i;
}
}
return max;
}