最大子段和問題(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,還可以擴展到三維情況,有興趣的讀者請參看:最大長方體問題。