雙周賽116。明明是很經典的01背包,誰知道OJ又出現什麼鳥問題,用@cache竟然給我炸MLE,害我名次直接噴掉50名。

題目

輸入整數陣列nums,還有整數target。

回傳總和正好為target的子陣列的最大子序列長度。若不存在則回傳-1。

解法

反正就把nums[i]當作物品大小,target是背包空間,求正好裝滿背包最多可以拿幾個物品。

定義dp(i,remain):前i個物品中,正好裝滿remain空間最多可以拿幾個物品。
轉移方程式:dp(i,remain)=max( dp(i-1,remain), 1+dp(i-1,remain-num[i]) )
base cases:當i<0且remain正好為0,代表剛好拿滿,回傳0;否則不合法,回傳-inf。

時間複雜度O(N*target)。
空間複雜度O(N*target)。

class Solution:
    def lengthOfLongestSubsequence(self, nums: List[int], target: int) -> int:
        N=len(nums)
        
        @cache
        def dp(i,remain):
            if i<0 and remain==0:
                return 0
            if i<0 or remain<0:
                return -inf
            res=dp(i-1,remain) # no take
            res=max(res,1+dp(i-1,remain-nums[i])) # take
            return res
        
        ans=dp(N-1,target)
        dp.cache_clear() # prevent MLE
        
        if ans==-inf:
            return -1
        
        return ans

如果說是遞迴造成額外的記憶體開銷才MLE,那就算了。
但是自己手寫記憶化竟然又可以過。

class Solution:
    def lengthOfLongestSubsequence(self, nums: List[int], target: int) -> int:
        N=len(nums)
        memo=[[None]*(target+1) for _ in range(N)]
        
        def dp(i,remain):
            if i<0 and remain==0:
                return 0
            if i<0 or remain<0:
                return -inf
            if memo[i][remain] is None:
                res=dp(i-1,remain) # no take
                res=max(res,1+dp(i-1,remain-nums[i])) # take
                memo[i][remain]=res
            return memo[i][remain]
        
        ans=dp(N-1,target)
        
        if ans==-inf:
            return -1
        
        return ans

改成遞推當然也可以過。

class Solution:
    def lengthOfLongestSubsequence(self, nums: List[int], target: int) -> int:
        N=len(nums)
        dp=[[-inf]*(target+1) for _ in range(N+1)]
        dp[0][0]=0
        
        for i,x in enumerate(nums):
            for remain in range(target+1):
                res=dp[i][remain]
                if remain>=x:
                    res=max(res,1+dp[i][remain-x])
                dp[i+1][remain]=res
        
        ans=dp[N][target]
        if ans==-inf:
            return -1
        
        return ans

老樣子,dp只會依賴於上一個i的狀態,只要保留上次的結果就行。
而且remain也只依賴於較小值,可以倒序更新。

時間複雜度O(N*target)。
空間複雜度O(target)。

class Solution:
    def lengthOfLongestSubsequence(self, nums: List[int], target: int) -> int:
        N=len(nums)
        dp=[-inf]*(target+1)
        dp[0]=0
    
        for i,x in enumerate(nums):
            for remain in reversed(range(x,target+1)):
                dp[remain]=max(dp[remain],1+dp[remain-x])
        
        ans=dp[target]
        if ans==-inf:
            return -1
        
        return ans