題目:輸入一個整型數組,數組里有正數也有負數。數組中一個或連續的多個整數組成一個子數組。求所有子數組的和的最大值。要求時間負責度為O(n)。
看到這個題目,我們首先想到的是求出這個整型數組所有連續子數組的和,長度為n的數組一共有 n(n+2)/2個子數組,因此要求出這些連續子數組的和最快也需要O(n^2)的時間復雜度。但是題目要求的O(n)的時間復雜度,因此上述思路不能解決問題。
看到O(n)時間復雜度,我們就應該能夠想到我們只能對整個數組進行一次掃描,在掃描過程中求出最大連續子序列和以及子序列的起點和終點位置。假如輸入數組為{1,-2,3,10,-4,7,2,-5},我們嘗試從頭到尾累加其中的正數,初始化和為0,第一步加上1,此時和為1,第二步加上-2,此時和為-1,第三步加上3,此時我們發現-1+3=2,最大和2反而比3一個單獨的整數小,這是因為3加上了一個負數,發現這個規律以后我們就重新作出累加條件:如果當前和為負數,那么就放棄前面的累加和,從數組中的下一個數再開始計數。
代碼實例:

#include<iostream> #include<stdlib.h> using namespace std; //求最大連續子序列和 int FindGreatestSumOfSubArray(int arry[],int len) { if(arry==NULL||len<=0) return -1; int start=0,end=0;//用於存儲最大子序列的起點和終點 int currSum=0;//保存當前最大和 int greatestSum=-10000;//保存全局最大和 for(int i=0;i<len;i++) { if(currSum<0)//如果當前最大和為負數,則舍棄前面的負數最大和,從下一個數開始計算 { currSum=arry[i]; start=i; } else currSum+=arry[i];//如果當前最大和不為負數則加上當前數 if(currSum>greatestSum)//如果當前最大和大於全局最大和,則修改全局最大和 { greatestSum=currSum; end=i; } } cout<<"最大子序列位置:"<<start<<"--"<<end<<endl; return greatestSum; } void main() { int arry[]={1,-2,3,10,-4,7,2,-5}; int len=sizeof(arry)/sizeof(int); //cout<<len<<endl; int sum= FindGreatestSumOfSubArray(arry,len); cout<<"最大子序列和:"<<sum<<endl; system("pause"); }
更正連續子序列位置錯誤的問題(PS:2012-8-10)
經過@Yu's 技術生涯 測試,發現我上面的代碼確實存在問題,現在做了如下修改:
- 1.添加了一個遍歷用於保存遍歷數組中發現的最大和的起始,原來的start和end只用於保存真是的開始於結尾。
- 2.當currSum<0的時候,我們只讓p值為最大和子數組的開始
- 3.在最后判斷currSum>greatestSum的時候,只有當currSum>greatestSum成立,才讓start=p,否則就表明以p開頭的子數組最大和不是最大的。
示例代碼如下:

#include<iostream> #include<stdlib.h> using namespace std; //求最大連續子序列和 int FindGreatestSumOfSubArray(int arry[],int len) { if(arry==NULL||len<=0) return -1; int start=0,end=0;//用於存儲最大子序列的起點和終點 int p=0;//指針,用於遍歷數組。 int currSum=0;//保存當前最大和 int greatestSum=-10000;//保存全局最大和 for(int i=0;i<len;i++) { if(currSum<0)//如果當前最大和為負數,則舍棄前面的負數最大和,從下一個數開始計算 { currSum=arry[i]; p=i; } else currSum+=arry[i];//如果當前最大和不為負數則加上當前數 if(currSum>greatestSum)//如果當前最大和大於全局最大和,則修改全局最大和 { greatestSum=currSum; start=p; end=i; } } cout<<"最大子序列位置:"<<start<<"--"<<end<<endl; return greatestSum; } void main() { //int arry[]={1,-2,3,10,-4,7,2,-5}; int arry[]={1,-2,3,10,-4,7,2,-19,2}; int len=sizeof(arry)/sizeof(int); //cout<<len<<endl; int sum= FindGreatestSumOfSubArray(arry,len); cout<<"最大子序列和:"<<sum<<endl; system("pause"); }
使用動態規划方法(PS:2012-10-9)
解體思路:
如果用函數f(i)表示以第i個數字結尾的子數組的最大和,那么我們需要求出max(f[0...n])。我們可以給出如下遞歸公式求f(i)
這個公式的意義:
- 當以第(i-1)個數字為結尾的子數組中所有數字的和f(i-1)小於0時,如果把這個負數和第i個數相加,得到的結果反而不第i個數本身還要小,所以這種情況下最大子數組和是第i個數本身。
- 如果以第(i-1)個數字為結尾的子數組中所有數字的和f(i-1)大於0,與第i個數累加就得到了以第i個數結尾的子數組中所有數字的和。
代碼實現

//使用動態規划求最大連續子數組和 int FindGreatestSumOfSubArray2(int arry[],int len,int c[]) { c[0]=arry[0]; int start,end; int temp=0; int maxGreatSum=-100; for(int i=1;i<len;i++) { if(c[i-1]<=0) { c[i]=arry[i]; temp=i; } else c[i]=arry[i]+c[i-1]; if(c[i]>maxGreatSum) { maxGreatSum=c[i]; start=temp; end=i; } } //輸出c[i] for(int i=0;i<len;i++) cout<<c[i]<<" "; cout<<endl; cout<<"最大子序列位置:"<<start<<"--"<<end<<endl; return maxGreatSum; }
其實上述兩種方法的實現方式非常相似,只是解體思路不同而已。通常我們會使用遞歸的方式分析動態規划的問題,但是最終都會基於循環去寫代碼。在動態規划方法中創建了一個數組c[]用於存儲中間結果,而第一種方法中只需要一個臨時變量currSum.