DP教學系列。被python內建的cache坑了好幾次TLE,連MLE都出現,不斷調整cache大小才過,太噁心了。

題目

輸入長度N的陣列nums,及長度M的陣列multipliers,N>=M。
你必須執行M次乘法動作,可以選擇nums中最前或是最後的數和multipliers[i]相乘,並獲得分數。nums中使用過的頭或尾必須丟掉,求最多可獲得多少分數。

解法

步驟1:定義狀態

直覺想用top-down方式,一開始我以dp(i,left,right)表示第i次動作,left、right表示nums可用的最左右方元素位置。

步驟2:找出狀態轉移方程式

對於第i,left,right狀態,要先查看動左邊還是右邊之後造成的影響,何者能夠獲得更大利潤空間,並加上當回合選擇的得分。
所以dp(i,left,right)=max(左數乘積+dp(i+1, left+1, right),右數乘積+dp(i+1, left, right+1))。

步驟3:處理base cases

當i=M-1時,表示處理到最後一個數字了,不必再考慮未來收入,直接回傳max(左數乘積,右數乘積)即可。

後來想到right可以通過計算得到,有點類似之前的撿櫻桃,可以節省一個變數,進而將空間從M^3降到M^2。最後變成dp(i,left)。
程式碼如下,因為懶得手動刻記憶化,結果吃了一堆紅字,得不償失。晚點再更新其他版本。

class Solution:
    def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
        N = len(nums)
        M = len(multipliers)

        @lru_cache(2000)
        def dp(i, left):
            right = N-1-(i-left)
            if i == M-1:
                return max(nums[left]*multipliers[i], nums[right]*multipliers[i])
            else:
                return max(nums[left]*multipliers[i]+dp(i+1, left+1),
                           nums[right]*multipliers[i]+dp(i+1, left))

        return dp(0, 0)

自己做記憶化還是超時,不太確定為什麼,只好改成bottom-up方式。

class Solution:
    def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
        N = len(nums)
        M = len(multipliers)
        dp = [[0]*(M+1) for _ in range(M+1)]

        for i in range(M-1, -1, -1):
            for left in range(i, -1, -1):
                right = N-1-(i-left)
                dp[i][left] = max(nums[left]*multipliers[i]+dp[i+1][left+1],
                                  nums[right]*multipliers[i]+dp[i+1][left])

        return dp[0][0]

2022-09-17更新。
今天每日題又輪到這,一看到我按過爛就知道這題有點問題。
雖然python做top down還是一樣會TLE,但這次定義的dp狀態和之前有些許不同。
之前是紀錄第i次動作中,有幾次left次是選擇左邊,借而推算出右邊界。而今天變成紀錄左右選擇的次數來推算出是第i次動作。
而base case是:總共完成M次動作之後,無法繼續選擇,故回傳0。

class Solution:
    def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
        N,M=len(nums),len(multipliers)
        
        @cache
        def dp(left,right):
            if left+right==M:
                return 0
            return max(nums[left]*multipliers[left+right]+dp(left+1,right),
                    nums[N-1-right]*multipliers[left+right]+dp(left,right+1))
            
        return dp(0,0)

同樣的概念換到java做記憶化則可以順利AC。

class Solution {
    int M,N;
    Integer memo[][];
    int nums[];
    int mul[];
    
    public int maximumScore(int[] nums, int[] multipliers) {
        this.N=nums.length;
        this.M=multipliers.length;
        this.nums=nums;
        this.mul=multipliers;
        this.memo=new Integer[this.M+5][this.M+5];
        return dp(0,0);
    }
    
    int dp(int left, int right){
        if(left+right==M){
            return 0;
        }        
        if(memo[left][right]==null){
            int l=nums[left]*mul[left+right]+dp(left+1,right);
            int r=nums[N-1-right]*mul[left+right]+dp(left,right+1);
            memo[left][right]=Math.max(l,r);
        }
        return memo[left][right];
    }
}

我還是不死心,試著把python改成bottom up的方式,總算是順利通過,還勝過98%的提交。

class Solution:
    def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
        N,M=len(nums),len(multipliers)
        dp = [[0]*(M+5) for _ in range(M+5)]
        
        for left in range(M-1,-1,-1):
            for right in range(M-left-1,-1,-1):
                dp[left][right]=max(nums[left]*multipliers[left+right]+dp[left+1][right],
                    nums[N-1-right]*multipliers[left+right]+dp[left][right+1])
        
        return dp[0][0]