求連續子陣列的最大和

2021-09-08 03:10:00 字數 3439 閱讀 4887

整理自

求子陣列的最大和

題目描述:

輸入乙個整形陣列,陣列裡有正數也有負數。

陣列中連續的乙個或多個整數組成乙個子陣列,每個子陣列都有乙個和。

求所有子陣列的和的最大值。要求時間複雜度為o(n)。

例如輸入的陣列為1, -2, 3, 10, -4, 7, 2, -5,和最大的子陣列為3, 10, -4, 7, 2,

因此輸出為該子陣列的和18。

思路一. 動態規劃

設sum[i]為以第i個元素結尾且和最大的連續子陣列。假設對於元素i,所有以它前面的元素結尾的子陣列的長度都已經求得,那麼以第i個元素結尾且和最大的連續子陣列實際上,要麼是以第i-1個元素結尾且和最大的連續子陣列加上這個元素,要麼是只包含第i個元素,即sum[i] = max(sum[i-1] + a[i], a[i])。可以通過判斷sum[i-1] + a[i]是否大於a[i]來做選擇,而這實際上等價於判斷sum[i-1]是否大於0。由於每次運算只需要前一次的結果,因此並不需要像普通的動態規劃那樣保留之前所有的計算結果,只需要保留上一次的即可,因此演算法的時間和空間複雜度都很小。

偽**如下

result = a[1

]sum = a[1

]for i: 2

to length[a]

if sum > 0

sum +=a[i]

else

sum =a[i]

if sum >result

result =sum

return result

二. 掃瞄法

(後加註:這裡提到的掃瞄法存在乙個問題就是如果最大欄位和小於0則演算法沒法給出正確答案。其實這個問題用動態規劃就好,這裡的掃瞄法其實真的不是個好方法,只是因為很有名所以還是粘出來了)

當我們加上乙個正數時,和會增加;當我們加上乙個負數時,和會減少。如果當前得到的和是個負數,那麼這個和在接下來的累加中應該拋棄並重新清零,不然的話這個負數將會減少接下來的和。實現:

//

//updated,2011.05.25.

#include int maxsum(int* a, int

n)

return

sum;

}

intmain()

;

//int a=;

//測試全是負數的用例

cout<8)

0;

}

/*-------------------------------------

解釋下:

例如輸入的陣列為1, -2, 3, 10, -4, 7, 2, -5,

那麼最大的子陣列為3, 10, -4, 7, 2,

因此輸出為該子陣列的和18。

所有的東西都在以下倆行,

即: b : 0 1 -1 3 13 9 16 18 13

sum: 0 1 1 3 13 13 16 18 18

其實演算法很簡單,當前面的幾個數,加起來後,b<0後,

把b重新賦值,置為下乙個元素,b=a[i]。

當b>sum,則更新sum=b;

若b

(後加註:前面的**是貼上別人的,下面的證明是我自己鼓搗的。之後由於看到了動態規劃法,覺得這個證明實在是沒什麼必要看了。在後來練習了很多數學證明發現,證明是越精煉越好,不過這種敢於窮舉情況的思路還是很好的,很多時候難的證明題只要多窮舉幾種情況再加以精煉往往就能做出,大不了多舉幾個情況問題也能解決)

據說這道題是《程式設計珠機》裡面的題目,叫做掃瞄法,速度最快,掃瞄一次就求出結果,複雜度是o(n)。書中說,這個演算法是乙個統計學家提出的。

這個演算法如此精煉簡單,而且複雜度只有線性。但是我想,能想出來卻非常困難,而且證明也不簡單。在這裡,我斗膽寫出自己證明的想法:

關於這道題的證明,我的思路是去證明這樣的掃瞄法包含了所有n^2種情況,即所有未顯示列出的子陣列都可以在本題的掃瞄過程中被拋棄。

1 首先,假設演算法掃瞄到某個地方時,始終未出現加和小於等於0的情況。

我們可以把所有子陣列(實際上為當前掃瞄過的元素所組成的子陣列)列為三種:

1.1 以開頭元素為開頭,結尾為任一的子陣列

1.2 以結尾元素為結尾,開頭為任一的子陣列

1.3 開頭和結尾都不等於當前開頭結尾的所有子陣列

1.1由於遍歷過程中已經掃瞄,所以演算法已經考慮了。1.2確實沒考慮,但我們隨便找到1.2中的某乙個陣列,可知,從開頭元素到這個1.2中的陣列的加和大於0(因為如果小於0就說明掃瞄過程中遇到小於0的情況,不包括在大前提1之內),那麼這個和一定小於從開頭到這個1.2陣列結尾的和。故此種情況可捨棄

1.3 可以以1.2同樣的方法證明,因為我們的結尾已經列舉了所有的情況,那麼每一種情況和1.2是相同的,故也可以捨棄。

2 如果當前加和出現小於等於0的情況,且是第一次出現,可知前面所有的情況加和都不為0

乙個很直觀的結論是,如果子段和小於0,我們可以拋棄,但問題是是不是他的所有以此子段結尾為結尾而開頭任意的子段也需要拋棄呢?

答案是肯定的。因為以此子段開頭為開頭而結尾任意的子段加和都大於0(情況2的前提),所以這些子段的和是小於當前子段的,也就是小於0的,對於後面也是需要拋棄的。也就是說,所有以之前的所有元素為開頭而以當前結尾之後元素為結尾的陣列都可以拋棄了。

而對於後面拋棄後的陣列,則可以同樣遞迴地用1 2兩個大情況進行分析,於是得證。

這個演算法的證明有些複雜,現在感覺應該不會錯,至少思路是對的,誰幫著在表達上優化下吧。:-)

三. 分治,合併的時候窮舉

(這個也是直接從原帖抄的,思路應該不難理解。鑑於複雜度高又遞迴,實際解決問題時不建議採用)

//

algorithm 3:時間效率為o(n*log n)

//演算法3的主要思想:採用二分策略,將序列分成左右兩份。

//那麼最長子序列有三種可能出現的情況,即

//只出現在左部分.

//只出現在右部分。

//出現在中間,同時涉及到左右兩部分。

//分情況討論之。

static

int maxsubsum(const

int a,int left,int

right)

maxrightbordersum=0

; rightbordersum=0

;

for(i=center+1;i<=right;i++)

int max1=maxleftsum>maxrightsum?maxleftsum:maxrightsum;

int max2=maxleftbordersum+maxrightbordersum;

return max1>max2?max1:max2;

}

求連續子陣列最大和

輸入乙個整形陣列,陣列裡有正數也有負數。陣列中連續的乙個或多個整數組成乙個子陣列,每個子陣列都有乙個和。求所有子陣列的和的最大值。例如輸入的陣列為 2,11,4,13,5,2 和最大的子陣列為11,4,13 因此輸出為該子陣列的和20。本題解法多種多樣,時間複雜度可以為 o n 3 o n 2 o ...

求連續子陣列的最大和

題目一 給定乙個陣列,求該陣列的最大子陣列和 與求最長連續子陣列互為姊妹題 題目二 給定乙個陣列,求該陣列的最大兩個子陣列的和 子陣列位置無重合 解題一 子陣列的最大和 技巧法 func getmaxsum arr int int res intmin cur 0 for ele range arr...

求連續子陣列的最大和

題目 在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如 連續子向量的最大和為8 從第0個開始,到第3個為止 思路 從頭到尾逐個累加,儲存兩個值 當前和 最大和。public c...