【DP】乘積最大子陣列

peterzh6發表於2024-05-04

題源

思路和演算法

如果我們用 fmax(i) 來表示以第 i 個元素結尾的乘積最大子陣列的乘積,a 表示輸入引數 nums,那麼根據「53. 最大子序和」的經驗,我們很容易推匯出這樣的狀態轉移方程:

fmax(i) = max{f(i-1)×a[i], a[i]}

它表示以第 i 個元素結尾的乘積最大子陣列的乘積可以考慮 a[i] 加入前面的 fmax(i-1) 對應的一段,或者單獨成為一段,這裡兩種情況下取最大值。求出所有的 fmax(i) 之後選取最大的一個作為答案。

分析錯誤與修正

這樣做本來看似沒有問題,但是如果 a = {5, 6, -3, 4, -3},按照前面的演算法可以得到答案為 30,即前兩個數的乘積。實際上,更大的乘積是包含全體數字的乘積。問題出在哪裡呢?最後一個 -3 所對應的 fmax 的值既不是 -3,也不是 4×(-3),而是 5×6×(-3)×4×(-3)。這說明當前位置的最優解未必是由前一個位置的最優解轉移得到的。

正負性的分類討論

考慮當前位置如果是一個負數的話,我們希望以它前一個位置結尾的某個段的積也是個負數,這樣就可以負負得正,並且我們希望這個積儘可能「負得更多」,即儘可能小。如果當前位置是一個正數的話,我們更希望以它前一個位置結尾的某個段的積也是個正數,並且希望它儘可能地大。因此,我們維護一個 fmin(i),它表示以第 i 個元素結尾的乘積最小子陣列的乘積,從而得到以下的動態規劃轉移方程:

fmax(i) = max{fmax(i-1)×a[i], fmin(i-1)×a[i], a[i]}
fmin(i) = min{fmax(i-1)×a[i], fmin(i-1)×a[i], a[i]}

這樣,第 i 個元素結尾的乘積最大或最小子陣列的乘積可以透過加入第 i-1 個元素結尾的乘積最大或最小的子陣列的乘積中,二者加上 a[i],三者取大或小,決定第 i 個元素結尾的乘積最大或最小子陣列的乘積。

來源:力扣(LeetCode)

class Solution:
    def maxProduct(self, nums):
        if not nums:
            return 0
        
        maxF = nums[:]
        minF = nums[:]
        for i in range(1, len(nums)):
            maxF[i] = max(maxF[i - 1] * nums[i], nums[i], minF[i - 1] * nums[i])
            minF[i] = min(minF[i - 1] * nums[i], nums[i], maxF[i - 1] * nums[i])
        
        return max(maxF)

相關文章