詳解 最大子段和


題目名稱:最大子段和

 

題目描述:給出一段序列,選出其中連續且非空的一段使得這段和最大。

輸入格式:

第一行是一個正整數N,表示了序列的長度。

第2行包含N個絕對值不大於10000的整數A[i],描述了這段序列。

輸出格式:

僅包括1個整數,為最大的子段和是多少。子段的最小長度為1。

 

枚舉

 最蠢的辦法,枚舉左端點和右端點,再求和,用一個max儲存歷史的最大值,就可以了。時間復雜度O(n3)。

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int _Max(int x,int y){return x>y?x:y;}
 5 int num[10001],sum=0;
 6 int max=0;
 7 int main(){
 8     int n;
 9     scanf("%d",&n);
10     int i,j;
11     for(i=1;i<=n;i++){
12         scanf("%d",&num[i]);
13     }
14     for(i=1;i<n;i++){
15         for(j=i+1;j<=n;j++){
16             sum=0; 
17             for(int k=i;k<=j;k++)
18                 sum+=num[k];
19             max=_Max(sum,max);
20         }
21     }
22     printf("%d\n",max);
23     return 0;
24 }
View Code

在枚舉的基礎上,我們可以發現:求一個和為最大的子序列,那么作為任意一個子序列,若它的左端點是第i個元素,右端點是第j個元素,則它其中的元素之和可以表示為第1~j個元素和第1~i個元素之差。

這就是一個預處理。可以在枚舉的基礎上減少一些時間,但並不是最優,也是O(n2)。

 

分治

用分治求最大子段和應首先將這整個問題划分成規模更小的子問題,可以取中點,將這一個序列划分成長度相等的兩個序列。對於此時的最大子段和,會出現3種情況,即:

1.最大子段和在第一個序列中,即在我們所取的中點左邊。

2.最大子段和在第二個序列中,即在我們所取的中點右邊;

3.最大子段和在第一個序列與第二個序列之間,即我們取的這個點被包含在這個子段中。這時,我們就應該繼續以這個子段為基礎,繼續划分,知道不能划分為止。  

(其實上面兩種情況也是要划分的)(滑稽)

將3種情況的最大子段和合並,取三者之中的最大值就為問題的解。時間復雜度為O(nlogn)。

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int n,a[20001]; 
 5 int Sum(int left, int right){
 6     int sum=0;
 7     if(left==right){ 
 8         if(a[left]>0)
 9             sum=a[left];  
10         else sum=0;  
11     }  
12     else{  
13         int mid=(left+right)/2;
14         int l_sum=Sum(left,mid);
15         int r_sum=Sum(mid+1,right);
16         int s1=0,s2=0;
17         int lefts=0,rights=0;  
18         for(int i=mid;i>=left;i--){
19             lefts+=a[i];  
20             if(lefts>s1)s1=lefts;
21         }
22         for(int j=mid+1;j<=right;j++){  
23             rights+=a[j];
24             if(rights>s2)s2=rights;  
25         }
26         sum=s1+s2;
27         if(sum<l_sum)sum=l_sum;
28         if(sum<r_sum)sum=r_sum;
29     }
30     return sum;
31 }
32 int main(){
33     scanf("%d",&n);
34     int i,j;
35     for(i=1;i<=n;i++)
36         scanf("%d",&a[i]);
37     printf("%d",Sum(1,n));
38     return 0;
39 }
View Code

 

貪心

和動態規划(乖乖的先看下面吧)的思路類似,博主看了很久,覺得好像沒有區別,所以就不講思路了。自己看代碼思考吧。

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int main(){
 5     int n,max=-2147483645,num,sum=0;
 6     scanf("%d",&n);
 7     for(int i=1;i<=n;i++){
 8         scanf("%d",&num);
 9         sum+=num;
10         if(sum>max)max=sum;
11         if(sum<0)sum=0;
12     }
13     printf("%d",max);
14     return 0;
15 }
View Code

 

動態規划

枚舉以從1到i為右端點的子段,用數組number[i]來保存第i個元素的值,sum[i]來保存以第i個元素結尾的最大子段和。顯然,我們要求的就是sum整個數組中最大的一個數了。遞歸的邊界條件:若i=1,則sum[i]=1。因為只有它一個元素。若i>=1,最優的整個子段只有這個元素本身。因為sum[i]是用來保存以第i個元素為右端點的最大子段和,所以這時的最大子段和就是number[i]和number[i]+sum[i-1]的比較,比較取最大即可。為什么要加上sum[i-1]呢?因為相比於以第i-2個元素結尾的子段來說,以i-1個元素結尾的子段顯然要更長,結果更大的可能性也更大。狀態轉移方程:sum[i]=max(sum[i-1]+number[i],numberp[i]);  (i>1)

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int _Max(int x,int y){return x>y?x:y;}
 5 int number[200001],sum[200001];
 6 int main(){
 7     int i,j;
 8     int n,maxx=0;
 9     scanf("%d",&n);
10     for(i=1;i<=n;i++)
11         scanf("%d",&number[i]);
12     sum[1]=number[1];
13     maxx=sum[1];
14     for(i=2;i<=n;i++){
15         sum[i]=_Max(sum[i-1]+number[i],number[i]);
16         maxx=_Max(sum[i],maxx);
17     }
18     printf("%d",maxx);
19 }
View Code

 

其實這個題目還有很多方法,這里就不介紹其他的了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM