演算法構思與程式流程
Comment
有幾個條件限制,讓題目看起來比較混亂。其實找頭尾個一段與找中間一段是一樣的,因為兩者是互補的,找頭尾一段總和不超過lim就等價於找中間一段不小於totalsum-lim。這裡我們可以假設lim≤totalsum,否則將lim改成totalSum就好。講解本題的做法前,先看一下基本型的做法。
基本sliding window
這類型題目的基本型是找出中間一段總和不超過lim的最大總和。
由於是正整數,單一個區間往左右延伸時,其總和必然是單調遞增的。所以很自然地可以用sliding window (或稱雙指針)的方法來做。基本觀念很簡單,我們始終關注一個區間(window) [le,ri],將區間的右端逐步往右移,每次一格,並且調整左端來維護以下的迴圈不變性:
「此區間是右端=ri的條件下,總和不超過lim的最大區間。」
由於前面說到的單調性,假設前面一回合的迴圈不變性已經滿足,當右端往右移動一格時,區間的總和只會變大,它所對應到的最佳左端一定是大於等於之前的左端。我們只需要將左端往右移動到第一個讓總和不超過lim的位置即可。
演算法如下:
best = isum = 0
le = 1
for ri = 1 to n do
isum += a[ri]
while isum > lim do // move le
isum -= a[le]
le += 1
end while
best = max(best,isum)
end for
由於le與ri都只有往右移動,所以時間複雜度是線性的。
本題的思考方式
與基本型相比,本題有兩個變化:
要找的prefix與suffix,而非中間一段。
所選取的兩段中奇數與偶數個數必須相等。
其實原理還是一樣,為了滿足奇偶個數的條件,我們對每一個prefix與suffix以它們的奇數個數減去偶數個數(以下稱為dif值)次予以分類擺放,相同dif值的前綴和放在同一個串列。對於每一個後綴,它的dif值如果是d,那麼我們只能在dif值為−d的前綴中尋找與他的搭配,因為這樣奇數與偶數個數才會相等。
如何在對應的串列中找到最佳搭配的前綴呢?我們的目標是前綴與後綴的和不超過某個lim且越大越好,所以後綴和如果是s,我們要找的前綴就是其和 ≤lim−s
的最大值。
當然不能用線性搜尋或枚舉比對,因為時間太慢。記得我們的前綴放入串列中都是單調遞增的,所以可以用二分搜來找。
事實上不需要,俗話說:「連續二分搜不如一路爬過去」,我們由小到大產生所有的後綴和,所以要找的前綴和在每個串列中是單調下降的。每次我們只要在對應的串列中比對最後一個前綴和,如果這個前綴和太大(>lim−s),那麼它對之後的後綴也太大,我們可以直接將其刪除。
最後一個問題是如何儲存這些前綴和的串列呢?同一個串列的dif值是相同的,本題保證所有prefix的dif值的絕對值不超過M=2000,所以前綴的dif值的範圍是−M~M,我們可以用2M+1個串列來儲存,dif值為i的放在第i+M個串列。
整理一下,算法流程如下:由小到大,對於每一個prefix,計算其總和以及奇數個數減去偶數個數dif值),依照dif值放入對應的串列;由短到長掃描所有suffix,計算其總和s與dif值d。
在prefix dif值為−d的串列(就是第M−d的串列)中,由後往前刪除總和過大(>lim−s)的prefix sum。如果有滿足要求的答案(串列非空),就嘗試更新最佳解。
注意到,每一個串列我們只在其後增加元素或刪除最後一個元素,所以其實是當做stack在使用。