1. 程式人生 > 實用技巧 >動態規劃系列之二最大和子陣列

動態規劃系列之二最大和子陣列

動態規劃之最大和子陣列

給定一個整數陣列 nums ,找到一個具有最大和的子陣列(子陣列最少包含一個元素),返回其最大和

輸入: [-2,-1,-3,4,-1,2,1,-5]
輸出: 6
解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6。

解題思路

使用動態方程解題就需要題目符合動態方程解法,動態方程的兩個思路:

  1. 將大的問題拆解成小一點問題,小問題和大問題的解決思路是類似的
  2. 小問題之間的關係能完成大問題的最優解

建立數學模型

求陣列arr中最大和的子陣列,建立一個子陣列和的列表dp。其中dp[i]代表著以第i-1個元素結尾的陣列的最大和。

一般情況下,我們可以用dp[i]代表以第 i 個元素結尾的子陣列的最值,而遞推關係就是考慮和dp[i-1]之間的關係

邊界值

dp[i]代表這以第i-1個元素結尾的子陣列的最大和,所以dp[0]就是邊界值。當陣列只有一個元素時,該元素就是陣列的最大和

狀態轉移方程

狀態轉移方程是最難寫的,但是我們要迎難而上。下面分析一下該狀態如何轉移。

這裡用dp[i]代表以第 i 個元素結尾的子陣列的最大和,則max(dp[i])就是要求的最終結果,那麼關鍵是如何寫出遞推關係式。

轉化後模型:
求i=7結尾的最大和。求i=7時,要知道前面的前一個dp[7-1]的值。比較 dp[6] + nums[7] 和 nums[7] 的大小,大的那一個就是dp[7]的值,也就是以 第7個元素結尾的子陣列的最大值。

這裡求8個元素的最大和的子陣列,這個大問題可以拆解成小問題,即求8個元素的最大和的子陣列,繼續拆解可以為求7個元素的最大和的子陣列,一直到求1個元素的最大和子陣列。

-2,-1,-3,4,-1,2,1,-5
-2,-1,-3,4,-1,2,1
-2,-1,-3,4,-1,2
-2,-1,-3,4,-1
-2,-1,-3,4
-2,-1,-3
-2,-1
-2

小問題之間的聯絡,可以完成大問題的最優解。

-2 最大和就是-2
-2,-1 以-1結尾的最大和為-1,因為前i-1個元素+i元素的最大值為負數,所以最大值為當前-1
-2,-1,-3 以-3結尾的最大和為-3,前2個元素+i元素的最大值為-3,相比較當前值-3,最大值就是-3
-2,-1,-3,4 以4結尾的最大和為4,前3個元素的最大和-3為負數,但沒第4個元素,所以最大的和為4
-2,-1,-3,4,-1 以-1結尾的最大和為3,前4個元素的最大和為4,所以加上當前值能變大,最大和為3
-2,-1,-3,4,-1,2 以2結尾的最大和為5,前5個元素的最大和為3,所以加上當前值能變大,最大和為5
-2,-1,-3,4,-1,2,1 以1結尾的最大和為1,前6個元素的最大和為5,加上當前值為6,
-2,-1,-3,4,-1,2,1,-5 以-5結尾的最大和1,前7個元素的最大和為1,大於0,所以加上當前元素能變大,最大和為1

統計出所有以第i個元素結尾的最大和為

-2 -1 -3 4 -1 2 1 -5
-2 -1 -3 4 3 5 6 1

可以看出當以1結尾時,可以獲得最大和的子陣列,值為6

明顯的,因為我們考慮的子陣列以nums[i]結尾,那麼該陣列一定是包含nums[i]這個元素的,因此需要考慮兩種情況:即nums[i]單獨成為一段還是與前面的dp[i-1]一起構成子陣列,因此,可以得到如下的遞推關係式:

dp[i]=max(dp[i-1]+nums[i],nums[i])

或者說:
dp[i-1]代表著前i-1個元素組成的子陣列的最大和,那麼加上第i個元素時就有兩種情況:

  1. 第i個元素 大於 dp[i-1]+arr[i],那麼前i個元素的最大值為第i個元素的值
  2. 第i個元素 小於 dp[i-1]+arr[i],那麼前i個元素的最大值為前i-1個元素的值 + 第i個元素
dp[i] = max{arr[i], dp[i-1]+arr[i]}

python程式碼

input_list = [-2,-1,-3,4,-1,2,1,-5]
length = len(input_list)
dp = [0] * length

# 邊界值,如果只有一個元素,則第一個元素的最大值就是自身
dp[0] = input_list[0]

for i in range(1,len(input_list)):
    # if dp[i-1] + input_list[i] > input_list[i] ---> dp[i-1] > 0。或許前一種寫法更容易理解
    if dp[i-1] > 0:
        # 如果前i-1個元素的最大值大於0,那麼加上當前就能使得以當前值為結尾的最大和變大
        dp[i] = dp[i-1] + input_list[i]
    else:
        # 如果前i-1個元素的最大值小於0,那麼加上當前就能使得以當前值為結尾的最大和變小。以當前值為結尾的最大和就是自身組成的陣列
        dp[i] = input_list[i]

print(dp)
print(max(dp))

又可以寫成:

input_list = [-2,1,-3,4,-1,2,1,-5,4]

length = len(input_list)

# 構建一個dp陣列,用來儲存以第i個元素為結尾的最大子陣列之和
dp = [0] * length
# 邊界值,如果只有一個元素,則第一個元素的最大值就是自身
dp[0] = input_list[0]

for i in range(1,length):
    # 迴圈陣列,比較前i-1個數組最大值+自身 和自身的大小。
    # 如果大則表明前i-1個數組是正數,則可以繼續加上第i個
    # 如果小則表明前i-1個數組是負數,那麼相加肯定更小,所以以自身為新起點繼續往下走
    dp[i] = max(dp[i-1]+input_list[i],input_list[i])
    
print(max(dp))
     

小結

最長連續子序的解法精華在於 維護了一個dp陣列,該陣列中的每一個元素都代表著前i-1個元素能夠組成的最大值,只需要比較前i-1個元素+第i個元素的值與第i個元素的值的大小,就能得到第前i個元素能夠組成的子陣列的最大值。