最大子段和 | 最大子矩陣 | 最大M子段和

漆楚衡發表於2014-12-19

最大子段和

給定由n個整數(可能為負整數)組成的序列as = a0, a1, ... , a(n - 1),求該序列形如∑ak(k = l => r - 1)的子段和的最大值。對於全是負數的陣列最大子段和為0。

簡單方法

給定這個題目,首先可以想到列舉每一對可能的左右邊界[l, r)並計算其中的和sum[l, r),這樣一個方法可以實現O(n ^ 3)的演算法。

觀察到這之中的重複計算,我們可以預處理一下,建立一個陣列,其中存放所有的sum[0, r),計算sum[l, r)時利用sum[l, r) = sum[1, r) - sum[1, l),這樣可以將演算法優化到O(n ^ 2)。(典型的空間換時間

進一步分析

優化的重要思路是利用先前的計算(上面的預處理陣列是這一思想的極致),對於最優化問題,自然是思考如何利用子最優構造最優,也就是尋找最優子結構。

如果有最優解在區間[l, r)。那麼,對於l <= m <= r,我們可以發現sum[l, m) >= 0。否則,sum[m, r) >= sum[l, r)。同理:sum[m, r) >= 0

反之,對於任意1 <= m <= r,[l, m)的最優字首和[m, r)的最優字尾構成bst[l, r)。(最優子結構)

現在有原問題求bst[l, r),對於任意l < m < r,我們可以分解問題為

bst[l, r) = max(bst[l, m), bst[m, r), [l, m)中的最優字尾+[m, r)中的最優字首)

上面驚現了標準的分治法,不過看上去非常複雜,我們要找到子解的最優,同時維持兩端最優資訊……

仔細看看,這是因為m可以是(l, r)中的任意位置,如果把m選在r - 1,就有

bst[l, r) = max(bst[l, r - 1), [l, r - 1)中的最優字尾+a[r - 1])

這個遞推公式最重要的優化是將l排除出了計算過程,也就是說求bst[l, r)是r的變化過程,l是“常數”,有

bst(r) = max(bst(r - 1), maxSuffix(r - 1) + a[r - 1])

這個公式中的變數r是線性變化的,不過maxSuffix情況如何還沒分析。

通過上面的討論我們知道,maxSuffix(r) >= 0

maxSuffix(r) = max(0, maxSuffix(r - 1) + a[r - 1])

彙總

maxSuffix(r) = 0                                    r = 0
maxSuffix(r) = max(0, maxSuffix(r - 1) + a[r - 1])    r > 0

bst(r) = 0                                            r = 0
bst(r) = max(bst(r - 1), maxSuffix(r))                r > 0

整理思路

可以重述上面的思路:

  • 我們取每一個右端點r,計算最優字尾,其中的最大值即為最大和子區間
  • 最優字尾masSuffix(r)可以通過maxSuffix(r - 1)在常數時間算出
  • 演算法的複雜度為O(n)

最重要的:演算法的遞推不是建立在最優值觀點上的,而是最優字尾上的,雖然兩者的值相同。

二維推廣:最大子矩陣

二維上的最大和子區域可以直接轉化為一維問題。

在列上列舉[l, r),將zip[i] = sum(m[i][k]) (l <= k < r)看成是一個數列,就可以像上面那樣對zip求解了。

複雜度:O(a * b ^ 2)

多子段推廣:最大M子段和

給定as[0, n)和正整數m,求m個不相交的子段,使m個子段的總和最大。

直接從遞推出發

定義

dp[i][j] : 表示as[0, j)的i欄位和
oneSum[l, r) : 表示[l, r)上的最大單欄位和

dp[i][j] = max(dp[i - 1][k] + oneSum[k, j)) (i <= k < j)

通過上述遞推式可以寫出一個O(n ^ 4)時間的演算法。

觀察可以發現oneSum上的O(n)不是很必要,對於固定的l,可以打出整張oneSum[l, r)表:每次固定一個l,O(n)時間打出[l, r),總時間消耗O(n ^ 2)

現在可以在O(n ^ 3)時間內求解了,不過還可以繼續優化。

接下來的優化比較複雜:仔細觀察上面的遞推式,可以發現對於同一個i,j的求值是沒有相互關聯的。這給了我們一個訊號,我們能否發現j們的相互關係?

視線回到上方“整理思路”的部分,當時我們通過轉換視角建立了遞推方程。這裡呢?

對於固定的i,我們希望通過遞推的方式求出j,即[j-x] → [j]

考慮dp[i][j]dp[i][j - 1]的關係,借鑑一維時的思路,我們有

temp[j] = max(dp[i - 1][k]) (k <= j)

dp[i][j] = max(temp[j - 1], dp[i][j - 1]) + as[j - 1]
  • temp[j - 1] : 表示取dp[i - 1][k < j]中最優的做前i - 1段,第i段從j處另起

  • dp[i][j - 1] : 表示繼承dp[i][j - 1]流傳下來的第i段

  • temp[j]可在計算dp[i - 1]時計算,不增加複雜度

  • 這裡我一開始的思路里並沒有引入temp表,而是希望通過dp[i - 1][j - 1]來表示dp[i - 1]段最優子解,不過後來發覺dp[i - 1][j - 1]不一定具有temp[j - 1]的最優性,而且還無法表示跳躍(相鄰兩段之間有沒有納入子段中的部分)。才回頭看書上的步驟。

回頭看,這一遞推式的思路與maxSuffix是何其相似。

結語

最大M欄位問題一直是我智商不能抵達的地方,雖然每次跟著書上走可以大概理解,卻總是離不了書。這次獨立走的最遠,只有一點小錯誤(大麻煩)。希望這次算是真正理解了。

相關文章