494. 目標和

image.png

思考

由于數組和不超過1000,考慮到k有可能是負數,統(tǒng)計加上1000防止值為負導致越界
但是這樣仍然可能越界 需要再處理邊界
D:dp[i][k]=n 表示包括nums[i]能組成和為k的種數為n 得把和求出來
T:dp[i][k] = dp[i-1][k-nums[i]] + dp[i-1][k+nums[i]]
B: 只有第一個元素時 dp[0][1000-nums[0]] +=1 dp[0][1000+nums[0]] +=1

實現

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
       
        if not nums:
            return 0
        nums_len = len(nums)
        nums_sum = sum(nums)
        if S>nums_sum or S<-nums_sum or (S+nums_sum)%2!=0:
            return 0
        dp = [[0]*(2*1000+1) for _ in range(nums_len)] 
        dp[0][1000-nums[0]] +=1
        dp[0][1000+nums[0]] +=1
        for i in range(1,nums_len):
            for j in range(-nums_sum,nums_sum+1):
                p = dp[i-1][j+nums[i]+1000] if j+nums[i]+1000<2*1000+1 else 0
                n = dp[i-1][j-nums[i]+1000] if j-nums[i]+1000>=0 else 0
                dp[i][j+1000]=n+p
        return dp[nums_len-1][S+1000]

反思

由于dp[i][k]只依賴上一個狀態(tài),可以考慮用一維的滾動數組進行狀態(tài)壓縮,我們發(fā)現 dp和上一行的左右倆邊都可能有關系 無論正向還是逆向遍歷,都會導致nums[i]重復產生影響,我們可以用一個臨時數組來保持當前i的計算結果,i全部計算完成再更新掉原來的dp

實現

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        if not nums:
            return 0
        nums_len = len(nums)
        nums_sum = sum(nums)
        if S>nums_sum or S<-nums_sum or (S+nums_sum)%2!=0:
            return 0
        dp = [0]*(2*nums_sum+1) # 表示-sum到sum
        dp[nums_sum-nums[0]] +=1 # 注意初始是0 時的情況 這里得用加法進行更新
        dp[nums_sum+nums[0]] +=1
        for i in range(1,nums_len):
            next_ = [0]*(2*nums_sum+1)
            for j in range(-nums_sum,nums_sum+1):
                p = dp[j+nums[i]+nums_sum] if 0<=j+nums[i]+nums_sum<2*nums_sum+1 else 0
                n = dp[j-nums[i]+nums_sum] if 0<=j-nums[i]+nums_sum<2*nums_sum+1 else 0
                next_[j+nums_sum]=n+p
            dp=next_
        return dp[S+nums_sum]

再優(yōu)化

由于加法具有交換律,我們可以把加正號得數放在一起構成一個子集 P ,加負號的放在一起構成子集N,問題就變成了分割成倆分子集使得P-N=target
我們知道 P+N=sum(nums) 兩式相加 2P = target+sum(nums) 問題就變成了求 和為 (target+sum(nums))/2的一個子集有多少個 這是一個典型的0-1背包問題
剪枝 由公式可知 target+sum(nums)得是偶數
還可以運用0-1背包的空間優(yōu)化法

實現

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        if sum(nums) < S or (sum(nums) + S) % 2 == 1: return 0
        P = (sum(nums) + S) // 2
        dp = [1] + [0 for _ in range(P)] # 和為0的子集說明什么都不選
        for i in range(len(nums)):
            for j in range(P,-1,-1):
                dp[j] = dp[j]+dp[j-nums[i]] if j-nums[i]>=0 else dp[j] # 0-1背包 j代表容量 dp[j]=k 代表容量為j時的拿法有k種
        return dp[P]
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容