最大子序列和問題

原文鏈接 在hust快樂的學習 http://blog.csdn.net/hs794502825/article/details/7956730

問題描述:

給定一個整數序列,a0, a1, a2, …… , an(項可以爲負數),求其中最大的子序列和。如果所有整數都是負數,那麼最大子序列和爲0;

例如:對於序列-2, 11, -4, 13, -5, –2。 所求的最大子序列和爲20(從11到13,即從a1到a3)。

用於測試下面代碼的的主函數代碼如下:(注意要更改調用的函數名)

[cpp] view plain copy
  1. int main(int argc, char **argv)  
  2. {  
  3.      vector<int> a;  
  4.     a.push_back(-2);  
  5.     a.push_back(11);  
  6.     a.push_back(-4);  
  7.     a.push_back(13);  
  8.     a.push_back(-5);  
  9.     a.push_back(-2);  
  10.     int result;  
  11.     result = maxSubSum1(a); //在這裏更改調用的函數名 cout<<result<<endl; //正確結果爲 20 return 0;  
  12. }  


方法一:對所有的子序列求和,在其中找到最大的

這是最容易想到的,也是最直接的,就是對所有的子序列求和,代碼如下:

[cpp] view plain copy
  1. int maxSubSum1( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;  
  4.     for(int i=0; i<a.size(); i++ )  
  5.     {   
  6.         for(int j=i; j<a.size(); j++ )  
  7.         {   
  8.             int thisSum =0;  
  9.             forint k=i; k<=j; k++ )  
  10.             {   
  11.                 thisSum += a[k];   
  12.             }  
  13.             if(thisSum>maxSum)  
  14.             {   
  15.                 maxSum = thisSum;  
  16.             }  
  17.         }  
  18.     }  
  19.     return maxSum;  
  20. }  


分析:

① 三層循環嵌套,時間複雜度爲O(n^3)

② 包含有大量的重複計算,例如i=1時: 當 j=3,則計算a1+a2+a3;j=4,則計算a1+a2+a3+a4;其中a1+a2+a3是重複計算的。

另一種思路:上面的方法在每一次循環中,固定i,並把a[i]當做起點,下面的方法將a[i]當做終點。

[cpp] view plain copy
  1. int maxSubSum1_2( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;   
  4.     for(int i=0; i<a.size(); i++ )  
  5.     {   
  6.         for(int j=0; j<i; j++ )  
  7.         {   
  8.             int thisSum =0;   
  9.             for(int k=j; k<=i; k++)  
  10.             {   
  11.                 thisSum +=a[k]; //從節點j開始 累加到節點i  
  12.                 if(thisSum>maxSum)  
  13.                 {   
  14.                     maxSum = thisSum;   
  15.                 }  
  16.             }  
  17.         }  
  18.     }  
  19.     return maxSum;   
  20. }  


方法二:從某點開始的所有序列中,找最大的

如果你意識到,子序列總要有一個位置開始,那麼變換一下循環方式,只要求出在所有位置開始的子序列,找到最大的。代碼如下:

[cpp] view plain copy
  1. int maxSubSum2( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;  
  4.     for(int i=0; i<a.size(); i++ )  
  5.     {   
  6.         int thisSum =0;  
  7.         for(int j=i; j<a.size(); j++ )  
  8.         {   
  9.             thisSum +=a[j]; //從節點i開始 累加到結尾  
  10.             if(thisSum>maxSum)  
  11.             {   
  12.                 maxSum = thisSum;   
  13.             }  
  14.         }  
  15.     }  
  16.     return maxSum;  
  17. }  


分析:

① 兩層循環嵌套,時間複雜度爲O(n^2)

② 雖然比方法一要少,但同樣包含重複計算。例如:當i=1時,要計算a1+a2+a3+a4+……;當i=2時,要計算a2+a3+a4+……;其中a2+a3+a4+……是重複的。

注意:下面是一個錯誤的方法,因爲它的起始點固定了,每次都從a0開始,是不能保證遍歷所有的子序列的。

[cpp] view plain copy
  1. int maxSubSum2_2( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;   
  4.     for(int i=0; i<a.size(); i++ )  
  5.     {   
  6.         int thisSum =0;  
  7.         for(int j=0; j<=i; j++ )  
  8.         {   
  9.             thisSum +=a[j]; //從節點0開始 累加到節點i  
  10.             if(thisSum>maxSum)  
  11.             {   
  12.                 maxSum = thisSum;   
  13.             }  
  14.         }  
  15.     }  
  16.     return maxSum;  
  17. }  


如果希望將固定終點,那麼計算的時候就要從終點開始,依次往前累加。代碼如下:

[cpp] view plain copy
  1. int maxSubSum2_3( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;   
  4.     for(int i=0; i<a.size(); i++ )  
  5.     {   
  6.         int thisSum =0;  
  7.         for(int j=i; j>=0; j-- )  
  8.         {//從節點i開始,向前累加到結尾0  
  9.             thisSum +=a[j];  
  10.             if(thisSum>maxSum)  
  11.             {   
  12.                 maxSum = thisSum;   
  13.             }  
  14.         }  
  15.     }  
  16.     return maxSum;  
  17. }  


方法三:從某一個正數開始

第一步:

到目前爲止,題目的信息你只用到了:“最小子序列之和爲0”(若有一項大於0,那麼子序列的和一定大於或等於該項,也就大於0;因爲若所有項都是負數,那麼結果爲0

如果你再挖掘一下題意:你就會發現,如果a[i]是負的,那麼a[i]一定不是最終所有結果子序列的起始點。代碼可以改造爲:

[cpp] view plain copy
  1. int maxSubSum3_1( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;  
  4.     for(int i=0; i<a.size(); i++ )  
  5.     { //(相對方法2,新增)如果a[i]<=0,那麼a[i]一定不是所要求的起點,所以直接跳過去(利用for循環中有i++)  
  6.         if( a[i]<=0 )  
  7.           continue;  
  8.         int thisSum =0;  
  9.         for(int j=i; j<a.size(); j++ )  
  10.         {   
  11.             thisSum +=a[j];  
  12.             if(thisSum>maxSum)  
  13.             {   
  14.                 maxSum = thisSum;   
  15.             }  
  16.         }  
  17.     }  
  18.     return maxSum;  
  19. }  


第二步:

如果你又再一步發現:任何負的子序列,不可能作爲最優子序列的前綴

又因爲上一步已經保證,序列以正數開頭a[i]>0,所以若a[i]到a[j]之間元素的序列和 thisSum<=0時,則i+1和j之間元素不會爲最優子序列的前綴,可以讓i=j,即不需要判斷在i和j之間元素開頭。代碼如下

[cpp] view plain copy
  1. int maxSubSum3_2( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;  
  4.     for(int i=0; i<a.size(); i++ )  
  5.     { //(相對方法2,新增)如果a[i]<=0,那麼a[i]一定不是所要求的起點,所以直接跳過去(利用for循環中有i++)  
  6.         if( a[i]<=0 )  
  7.           continue;  
  8.         int thisSum =0;  
  9.         for(int j=i; j<a.size(); j++ )  
  10.         {   
  11.             thisSum +=a[j];  
  12.             if(thisSum>maxSum)  
  13.             {   
  14.                 maxSum = thisSum;   
  15.             }  
  16.             else if( thisSum <= 0 )  
  17.             { //(相對方法3_1 新添) thisSum = 0; i = j; }  
  18.         }  
  19.     }  
  20.     return maxSum;  
  21. }  


第三步:

如果你又進一步發現:因爲要求序列開始元素大於0, 若以a[i]開頭的序列,a[i]>0,那麼可以知道,所求的最終子序列一定不會以a[i+1]開始, 因爲若到相同的元素終止,那麼從a[i]開始序列,一定大於從a[i+1]開始的序列。因爲s[i, k]=s[i+1, k]+a[i] 例如:a1+a2+a3>0,而又由於這時a1>0, 那麼所求子序列一定不會以a2開始,因爲從a1開始會更大。 更進一步,如若一個子序列thisSum>0(其中thisSum是從第m項到第n項的和),那麼序列一定不會以a[m]和a[n]之間的項開始。 因爲一直thisSum第一個元素a[m]是大於0的,且以a[m]開始的所有子序列都是大於0的,因爲若存在子序列小於0,就會提前返回了。 例如:若程序執行到thisSum (爲a2+a3+a4),若thisSum>0,則能說明,a2>0,並且a2+a3>0, a2+a3+a4>0。那麼 也可以跳過a[m]和a[n]之間的項,即另 i=j。

[cpp] view plain copy
  1. int maxSubSum3_3( const vector<int> &a )  
  2. {   
  3.     int maxSum = 0;  
  4.     for(int i=0; i<a.size(); i++ )  
  5.     { //(相對方法2,新增)如果a[i]<=0,那麼a[i]一定不是所要求的起點,所以直接跳過去(利用for循環中有i++)  
  6.         if( a[i]<=0 )  
  7.           continue;  
  8.         int thisSum =0;  
  9.         for(int j=i; j<a.size(); j++ )  
  10.         {   
  11.             thisSum +=a[j];  
  12.             if( thisSum>0 )  
  13.             { //(相對方法3_2 新添)  
  14.                 if(thisSum>maxSum)  
  15.                 { maxSum = thisSum; }  
  16.                 i = j; //(相對方法3_2 新添)  
  17.             }  
  18.             else if( thisSum <= 0 )  
  19.             { //(相對方法3_1 新添)   
  20.                 thisSum = 0;   
  21.                 i = j;  
  22.             }  
  23.         }  
  24.     }  
  25.     return maxSum;  
  26. }  


第四步:

最後如果你又瞭解一些程序結構上的優化的知識,那麼你會發現下面的問題:

① 循環的分支可以改變一下,去除嵌套分支結構。

② 判斷語句的分支中有共同部分,( i=j ),可以抽取出來。

以上兩步以後,循環部分的代碼編程 變成:

[cpp] view plain copy
  1. for(int i=0; i<a.size(); i++ )   
  2. {   
  3.       if( a[i]<=0 )   
  4.         continue;   
  5.       int thisSum =0;   
  6.       for(int j=i; j<a.size(); j++ )   
  7.       {   
  8.         thisSum +=a[j];   
  9.         if(thisSum>maxSum)   
  10.             { maxSum = thisSum; }   
  11.         else if( thisSum>0 )   
  12.             { //do nothing }   
  13.         else if( thisSum <= 0 )   
  14.             { thisSum = 0; }   
  15.             
  16.                 i = j;   
  17.       }   
  18. }  


③ 下面這步非常重要,如果你發現,內層循環的循環變量j 和 外層循環的循環變量i同步增長,那麼你是否能夠想到,外層循環可能沒有存在的必要。在這裏到底能不能去除外層循環,取決於外層循環中是否有額外的工作要做。這裏的額外工作是是if(a[i] <=0) 判斷語句,如果你能發現內層循環的 if(thisSum < 0)的判斷能夠替代 if( a[i]<=0 ) 的工作。因爲thisSum是由a[j]得到的。

到這裏,代碼就可以神奇的變爲如下的形式:常量空間,線性時間

[cpp] view plain copy
  1. int maxSubSum3_4( const vector<int> &a )   
  2. {   
  3.     int maxSum = 0;   
  4.     int thisSum = 0;   
  5.     for(int j=0; j<a.size(); j++ )   
  6.     {   
  7.         thisSum += a[j];   
  8.         if(thisSum>maxSum)   
  9.             { maxSum = thisSum; }   
  10.         else if( thisSum>0 )   
  11.             { //do nothing }   
  12.         else if( thisSum < 0 )   
  13.         { thisSum = 0; }   
  14.     }   
  15.     return maxSum;   
  16. }  


分析:

① 只有一層循環,時間複雜度爲O(n)常量空間,線性時間,這是最優解法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章