最大子段和问题(Maximum Interval Sum)
一.问题描述
给定长度为n的整数序列,a[1...n], 求[1,n]某个子区间[i , j]使得a[i]+…+a[j]和最大.或者求出最大的这个和.例如(-2,11,-4,13,-5,2)的最大子段和为20,所求子区间为[2,4]。
二.算法分析
1.穷举法
1 int start = 0;//起始位置
2 int end = 0; //结束位置
3 int max = 0;
4 for(int i = 1; i <= n; ++i)
5 {
6 for(int j = i; j <= n;++j)
7 {
8 int sum = 0;
9 for(int k = i; k <=j; ++k)
10 sum += a[k];
11 if(sum > max)
12 {
13 start = i;
14 end = j;
15 max = sum;
16 }
17 }
18 }
换一种穷举思路,对于起点 i,我们遍历所有长度为1,2,…,n-i+1的子区间和,以求得和最大的一个.这样也遍历了所有的起点的不同长度的子区间,同时,对于相同起点的不同长度的子区间,可以利用前面的计算结果来计算后面的。就像传递数组参数时往往传的不是起止位置,而是起始位置和长度。
1 int start = 0;//起始位置
2 int end = 0;//结束位置
3 int max = 0;
4 for(int i = 1; i <= n; ++i)
5 {
6 int sum = 0;
7 for(int j = i; j <= n;++j)
8 {
9 sum += a[j];
10 if(sum > max)
11 {
12 start = i;
13 end = j;
14 max = sum;
15 }
16 }
17 }
2.分治法
求子区间及最大和,从结构上是非常适合分治法的,因为所有子区间[start, end]只可能有以下三种可能性:
1.在[1, n/2]这个区域内
2.在[n/2+1, n]这个区域内
3.起点位于[1,n/2],终点位于[n/2+1,n]内
以上三种情形的最大者,即为所求. 前两种情形符合子问题递归特性,所以递归可以求出. 对于第三种情形,则必然包括了n/2和n/2+1两个位置,这样就可以利用第二种穷举的思路分别向左右扩张求出:
1 int maxInterval(int *a, int left, int right)
2 {
3 if(right==left)
4 return a[left]>0?a[left]:0;
5 int center = (left+right)/2;
6 int leftMaxInterval = maxInterval(a,left,center);
7 int rightMaxInterval= maxInterval(a,center+1,right);
8 int sum = 0;
9 int left_max = 0;
10 for(int i = center; i >= left; –i)
11 {
12 sum += a[i];
13 if(sum > left_max)
14 left_max = sum;
15
16 }
17 sum = 0;
18 int right_max = 0;
19 for(int i = center+1; i <= right; ++i)
20 {
21 sum += a[i];
22 if(sum > right_max)
23 right_max = sum;
24 }
25 int res = left_max+right_max;
26 if(res < leftMaxInterval)
27 res = leftMaxInterval;
28 if(res < rightMaxInterval)
29 res = rightMaxInterval;
30 return res;
31 }
3.动态规划并扩展到二维空间
令b[j]表示以位置 j 为终点的所有子区间中和最大的一个
子问题:如j为终点的最大子区间包含了位置j-1,则以j-1为终点的最大子区间必然包括在其中
如果b[j-1] >0, 那么显然b[j] = b[j-1] + a[j],用之前最大的一个加上a[j]即可,因为a[j]必须包含
如果b[j-1]<=0,那么b[j] = a[j] ,因为既然最大,前面的负数必然不能使你更大
1 #include<stdio.h>
2 #include<string.h>
3 int a[101][101],b[101],c[101];
4 int subsequencesum(int a[],int n)
5 {
6 int sum=0,maxsum=-0x7fffff,i;
7 for(i=1;i<=n;i++)
8 if(maxsum<a[i])
9 maxsum=a[i];
10 if(maxsum<=0)
11 return maxsum;
12 memset(c,0,sizeof(c));
13 for(i=1;i<=n;i++)
14 {
15 if(c[i-1]>0)
16 c[i] = c[i-1] + a[i];
17 else
18 c[i] = a[i];
19 if(c[i]>maxsum)
20 maxsum=c[i];
21 /*
22 sum+=a[i];
23 if(sum>maxsum)
24 maxsum=sum;
25 else
26 if(sum<0)
27 sum=0;
28 */
29 }
30 return maxsum;
31 }
32 int main()
33 {
34 int n,max,ans,temp;
35 int i,j,k,T,m;
36 while(~scanf("%d",&n))//说的是一组,实际却是多组
37 {
38 temp=ans=max=-0x7fffff;
39 for(i=1;i<=n;i++)
40 for(j=1;j<=n;j++)
41 scanf("%d",&a[i][j]);
42 for(i=1;i<=n;i++)
43 {
44 memset(b,0,sizeof(b));
45 for(j=i;j<=n;j++)
46 {
47 for(k=1;k<=n;k++)
48 {
49 b[k]+=a[j][k];
50 }
51 ans=subsequencesum(b,n);//算的是两列间的和,
52 if(temp<ans)
53 temp=ans;
54 }
55 }
56 printf("%d\n",temp);
57 }
58 //while(1);
59 return 0;
60 }
为加深理解,这里推荐练习一下HDU 1081,还可以扩展到三维情况,有兴趣的读者请参看:最大长方体问题。