最大子段和问题(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,还可以扩展到三维情况,有兴趣的读者请参看:最大长方体问题。