问题:给定一个数组,求最大连续子数组和,并输出开始和结束坐标。例如{-1,2,3,-2,5,-7,4,8,-2,1},最大连续子数组为{2,3,-2,5,-7,4,8},最大和为13,从下标1开始,到下标7结束。
一种思路是用DP。我们用一个数组max[]来记录以该元素为结尾的最大和,比如max[i]表示以arr[i]为结尾的最大连续子数组和。用一个begin[i]记录max[i]所对应的起始坐标。
对于max[i],max[i]= MAX{max[i-1] + arr[i], arr[i]},即以arr[i]结尾的最大连续子数组和要么是arr[i],要么是以arr[i-1]结尾的最大和max[i-1]加上arr[i]。
代码如下:
struct result{
int max;
int begin;
int end;
result(): max(0x80000000), begin(0), end(0) {}
};
result DP(int *arr, int n)
{
int max[n];
int begin[n];
int i;
for(i=0; i<n; ++i)
{
max[i] = arr[i];
}
for(i=1; i<n; ++i)
{
if((max[i-1]+ arr[i]) > arr[i])
{
max[i] = max[i-1]+ arr[i];
begin[i] = begin[i-1];
}
else
{
max[i] = arr[i];
begin[i] = i;
}
}
result rst;
rst.max = max[0];
for(i=1; i<n; ++i)
{
if(max[i] > rst.max)
{
rst.max = max[i];
rst.begin = begin[i];
rst.end = i;
}
}
return rst;
}
用DP做,需要额外的空间max[]和begin[]。下面介绍一种比较高效的做法。先看下代码:
result maxSubArray(int *arr, int n)
{
bool allNegative = true; //判断数组元素是否全是非正数
int i;
result rst;
for(i=0; i<n; i++)
{
if(arr[i] > rst.max)
{
rst.max = arr[i];
rst.begin = i;
rst.end = i;
}
if(arr[i] > 0)
{
allNegative = false;
break;
}
}
if(allNegative)
{
return rst;
}
int curr = 0;
rst.max = 0;
rst.begin = 0;
rst.end = 0;
bool flag = false;
int tempBegin = 0;
for(i=0; i<n; i++)
{
curr += arr[i];
if(flag && (curr > 0))
{
flag = false;
tempBegin = i;
}
if(curr > rst.max)
{
rst.max = curr;
rst.end = i;
rst.begin = tempBegin;
}
if(curr <= 0)
{
curr = 0;
flag = true;
}
}
return rst;
}
max表示最大连续和。当curr<0时,那么我们就令标志flag=true,以便在下一次curr>0时重设后备起点tempBegin。当curr大于0时,就做如下操作:当curr大于max时,就max=curr,并让begin=tempBegin,否则继续下一步。
这种做法有个问题,就是当数组元素全为非正数时就没发这么做。所以我们必须先预处理下,如果数组元素全为非正数时,就取最大的那个元素为max。