问题来源:《编程之美》2.14 求数组的子数组之和的最大值
一个有N个整型元素的一维数组(A[0], A[1], A[2], ...,A[N-1],求这个数组的子数组之和的最大值?
首先应该明确:
1.子数组应该是连续的,即子数组中的元素在原数组中是连续的。
2.题目只要求求和,没有要求返回子数组的位置
3.整型元素数组,则数组中可能包含正整数,0,负整数
方法1:
由题知,我们可以把所有子数组的和都求出来,再比较大小,这样肯定能够得出正确解。程序描述如下:
int maxSumOfSubArr(int array[], int size)
{
int maxsum = INT_MIN;
int sum;
for(int i = 0; i < size; ++i) {
sum = 0;
for(int j = i; j < size; ++j) {
sum += array[j];
if(sum > maxsum)
maxsum = sum;
}
}
return maxsum;
}
方法2:
利用动态规划算法,分析如下:
假定当前子数组的最大和存在于子数组(A[i], ..., A[j])中,我们分析遍历至A[0]元素时的子数组的最大和,此时子数组的最大和存在于下列三种情况中:
1.当 0 = i = j, 元素A[0]本身构成和最大的一段
2.当 0 = i < j, 和最大的一段以A[0]开始
3.当 0 < i,元素A[0]跟和最大的一段没有关系
于是可以得出:
Start[i] = max{A[i], A[i] + Start[i + 1]}, 这样先求出上面第1种和第2种情况的最大值
All[i] = max{Start[i], All[i + 1]},这样就求出了上面三种情况中的最大值
Start[i]中存储的是包含当前元素的子数组的和的最大值
All[i]中存储的是遍历至当前元素时,整个数组的子数组的和的最大值
int max(int x, int y)
{
return x > y ? x : y;
}
int maxSumOfSubArr(int array[], int size)
{
int nStart[size];
int nAll[size];
nStart[size - 1] = array[size - 1];
nAll[size - 1] = array[size - 1];
for(int i = size - 2; i >= 0; --i) {
nStart[i] = max(nStart[i], array[i] + nStart[i+1]);
nAll[i] = max(nStart[i], nAll[i+1]);
}
return nAll[0];
}
在上面的程序描述中,我们申请了两个数组来存储中间结果,根据递推公式,用两个变量就可以代替,修改后的程序描述如下:
int maxsum(int array[], int size)
{
int nStart = array[size - 1];
int nAll = array[size - 1];
for(int i = size - 2; i >= 0; --i) {
nStart = max(array[i], nStart + array[i]);
nAll = max(nStart, nAll);
}
return nAll;
}
也可写成如下形式:
int maxSum(int array[], int size)
{
int nStart = array[size - 1];
int nAll = array[size - 1];
for(int i = size - 2; i >= 0; --i) {
if(nStart < 0)
nStart = 0;
nStart += array[i];
if(nStart > nAll)
nAll = nStart;
}
return nAll;
}
现在如果对题目中增加一个要求,返回最大子数组的位置,怎么做?
从上面的程序描述中可以看出,在nStart < 0时,会重置nStart的值,相当于这时是一个新的子数组的起点,在nStart > nAll时,更新nAll的值,这时我们更新最大子数组的位置。程序描述如下:
//|*lIndex|, |*rIndex|用来存储和最大的子数组的位置
int maxSum(int array[], int size, int *lIndex, int *rIndex)
{
assert(array != NULL && size != 0);
//|nStart|和最大的一段子数组以当前元素开始
//|nAll|存储当前所有子数组的最大值
//|rStart|存储以当前元素开始的和最大的一段子数组的开始位置
int nStart = array[size - 1];
int nAll = array[size - 1];
*lIndex = size - 1;
*rIndex = size - 1;
int rStart = size - 1;
//从数组末尾往前遍历,直到数组首
for(int i = size - 2; i >= 0; --i) {
if(nStart < 0) {
nStart = 0;
//重新标记最大和的起点
rStart = i;
}
nStart += array[i];
if(nStart > nAll) {
nAll = nStart;
//更新最大和的范围
*lIndex = i;
*rIndex = rStart;
}
}
return nAll;
}