演算法 最大子陣列問題

2022-01-11 07:09:03 字數 3899 閱讀 8369

問題描述:

給定乙隻**在某段時間內的歷史**變化曲線,找出乙個能夠實現收益最大化的時間段。

理解:

為找出最大化的收益,需要考慮的是在買進和賣出時的**變化幅度,因此從該**的每日變化幅度來考慮問題比較合適。由此,可以將上述問題稍作變形:給定乙隻**在某段時間內的每日變化幅度,找出乙個合適的買進和賣出時間,以實現收益最大化。因此,將輸入資料轉換如下,並試圖在整個時間段中找到乙個累加和最大的子區間,亦即最大子陣列。

暴力求解方法:

首先能夠想到的是在乙個給定陣列(區間)中,其子陣列(子區間)的個數是c(2,n),很容易就能遍歷完所有子陣列從而找出最大的那個,其最壞情況漸進時間複雜度是θ(n2)。假設每日變化幅度儲存在陣列a中(a的下標從1到n),a.length表示a的元素個數,最終結果以元組形式返回;給出偽碼如下:

brute_force(a)

i = 1

sum = -infinity

for i <= a.length, inc by 1

j = i

last_sum = 0

for j <= a.length, inc by 1

last_sum += a[j]

if last_sum > sum

sum = last_sum

start = i

end = j

return (start, end, sum)

分治求解方法:

上述方法的漸進時間複雜度差強人意。模擬於歸併排序,有時採用分治策略能夠獲得更好的時間複雜度。分治策略通常包含分解成子問題、解決子問題、合併子問題。由此可以推出大致的解決思路:首先依然假設資料輸入如上乙個方法那樣,然後考慮將a[1...n]拆分為規模大致相同的兩個子陣列left[1...mid]和right[mid+1...n],其中mid=(1+n)/2向下取整,那麼可以肯定,最大子陣列要麼在這兩個子陣列中,要麼橫跨這兩個子陣列,因此可以分別求解這三種情況,取其中最大的子陣列並返回即可。

對於left/right子陣列可遞迴求解,而對於橫跨兩個子陣列的情況,如果能夠使得該情況下的求解時間複雜度為o(n),那麼應該能讓整體的最壞時間複雜度低於θ(n2)。如果僅僅是通過遍歷所有包含a[mid]和a[mid+1]的子陣列來找最大子陣列,那麼很顯然僅求解該情況就需要θ(n2)的時間。可以推斷橫跨兩個子陣列的最大子陣列,必須由兩個分別在left/right中的子陣列組成,這兩個子陣列在分別包含了a[mid]和a[mid+1]的所有子陣列中是最大的;因為如果存在乙個不滿足上述條件的最大子陣列,那麼總可以用上述方法找到乙個更大的子陣列。

根據上述思路,很容易推知求解橫跨兩個子陣列的情況只需要o(n)的時間。由此給出偽碼如下:

(1)子過程:找出橫跨兩個子陣列的最大子陣列

find_crossing_max_subarray(a, low, mid, high)

left_sum = -infinity

sum = 0

i = mid

for i >= low, dec by 1

sum += a[i]

if sum > left_sum

left_sum = sum

left_index = i

right_sum = -infinity

sum = 0

i = mid + 1

for i <= high, inc by 1

sum += a[i]

if sum > right_sum

right_sum = sum

right_index = i

return (left_index, right_index, left_sum+right_sum)

(2)主過程:分治法找出最大子陣列

find_max_subarray(a, low, high)

if low == high

return (low, high, a[low])

else

mid = down_trunc((low + high) / 2)

(left_start, left_end, left_sum) =

find_max_subarray(a, low, mid)

(right_start, right_end, right_sum) =

find_max_subarray(a, mid+1, high)

(cross_start, cross_end, cross_sum) =

find_crossing_max_subarray(a, low, mid, high)

if left_sum > right_sum and left_sum > cross_sum

return (left_start, left_end, left_sum)

else if right_sum > left_sum and right_sum > cross_sum

return (right_start, right_end, right_sum)

else

return (cross_start, cross_end, cross_sum)

可以看出上述演算法漸進時間複雜度為θ(nlg(n))。

縮減問題規模的方法:

在查詢過程中,是否可以根據現有的資訊,來縮減需要排查的子陣列個數,進而獲得更好的時間複雜度呢?乙個思路是不再重複檢查以前累加過的元素,即從左至右累加元素,儲存其中的最大子陣列,如果在加入乙個元素後累加和為負數,則從該元素的後乙個元素重新累加,直至整個陣列遍歷完畢。該思路有效的前提是證明以下幾個假設:

可以假設當從左至右累加直至累加和為負,所得的最大子陣列是當前已遍歷完的陣列部分中最大的

可以假設當累加和為負時,潛在的最大子陣列不可能從該元素或該元素左邊的元素開始

假設1不證自明。

假設從a[1]累加到a[i]時第一次遇到其累加和為負(1<=i<=n),那麼a[i]一定為負,且a[1]+...+a[i-1]>=0。當i<=2時,顯然此時假設2成立。當i>2時,可以認為在a[1]...a[i]中,所有子陣列可分為三種:從a[1]開始向右拓展、從a[i]開始向左拓展以及不包含a[1]和a[i]的中間子陣列;顯然從a[i]向左拓展的不可能是最大子陣列,而如果不包含a[1]和a[i]的中間子陣列是最大子陣列,那麼可以使該中間子陣列加上其左邊的部分構成乙個新的子陣列,而且該子陣列總是大於等於這個中間子陣列,因為其左邊部分總是大於等於0,所以該情況下假設2也得證。綜合來看假設2是成立的。

對於假設3,顯然潛在的最大子陣列不可能從a[i]開始,因為a[i]<0。當潛在的最大子陣列從a[i]的左邊開始時,假設其從a[j]開始(1<=j1時,a[j]+...+a[i]一定是負數,因為a[1]+...+a[j-1]一定大於等於0而a[1]+...+a[i]一定為負。所以綜合來看,從a[i]或者a[i]的左邊尋找潛在的子陣列是沒有意義的。

偽碼如下,時間複雜度為θ(n)。對於全部是負數的情況,特殊處理即可,不影響時間複雜度。

linear_search_max_subarray(a)

sum = -infinity

start = 0

end = 0

cur_sum = 0

cur_start_index = 1

i = 1

for i <= a.length, inc by 1

cur_sum += a[i]

if cur_sum < 0

cur_sum = 0

cur_start_index = i + 1

else

if sum < cur_sum

sum = cur_sum

start = cur_start_index

end = i

return (start, end, sum)

**自:

演算法 最大子陣列問題

今天我們要討論的是經典的問題 最大子陣列問題。題目如下 給定乙個陣列a 0,n 1 求a的連續子陣列,使得該陣列的和最大。例如 陣列 1,2,3,10,4,7,2,5 最大子陣列 3,10,4,7,2 這個問題已經算是比較經典的問題,這個問題有好幾種的求法,但是我們將去除暴力法,因為時間複雜度太高。...

最大子陣列問題 演算法導論

分治法思想 分解 子陣列一定被原陣列左邊或者右邊包含,或者跨越原陣列mid下標。解決 前兩種完全包含的情況形成子問題遞迴求解,並且縮小了問題規模,後一種是我們要解決的問題。合併 剩餘的問題是求跨越mid的最大子陣列,並且從三種情況中選出和最大的。另外 算導中偽 返回的是三元組,這裡實現的話用結構體返...

最大子陣列問題

顧名思義,最大子陣列問題是求乙個陣列array中 和最大的非空連續子陣列 這樣的連續子陣列我們叫做最大子陣列,它的應用也有 很多,比如說找出時間序列中兩個時間節點使得這兩個時間節點對應的值的落差最大,如下圖 對於這類問題,通過求原始時間序列的一階差分得到序列array,此時求得array的最大子陣列...