代码随想录算法训练营第四十四天丨完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

完全背包 卡码网第52题

一维dp:

import sys


def Knapsack(n, v, weights, values):
    dp = [0] * (v + 1)
    for i in range(n):
        for j in range(weights[i], v + 1):
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
    return dp[v]

    
if __name__== '__main__':
    weights, values = [],[]
    n, v = map(int, input().strip().split())
    for line in sys.stdin:
        weight, value = map(int, line.strip().split())
        weights.append(weight)
        values.append(value)
    print(Knapsack(n, v, weights, values))

二维dp:

import sys


def Knapsack(n, v, weights, values):
    dp = [[0] * (v + 1) for _ in range(n)]
    
    for i in range(n):
        for j in range(1, v + 1):
            if j < weights[i]:
                dp[i][j] = dp[i - 1][j]
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j  - weights[i]] + values[i])
            print(dp)
    return dp[n - 1][v]

    
if __name__== '__main__':
    weights, values = [],[]
    n, v = map(int, input().strip().split())
    for line in sys.stdin:
        weight, value = map(int, line.strip().split())
        weights.append(weight)
        values.append(value)
    print(Knapsack(n, v, weights, values))

518. 零钱兑换 II

感觉和目标和很像吼。

一维dp:

空集可以组成总额为0的金额,初始化dp[0] = 1。可以重复使用,因此正向更新一维dp数组可以重复使用。

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = [0] * (amount + 1)
        dp[0] = 1
        for coin in coins:
            for j in range(coin, amount + 1):
                dp[j] += dp[j - coin]
        return dp[amount]

二维dp:

加深理解,再写一版二维dp实现。

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = [[0] * (amount + 1) for _ in range(len(coins) + 1)]
        dp[0][0] = 1
        for i in range(1, len(coins) + 1):
            for j in range(amount + 1):
                dp[i][j] = dp[i - 1][j]
                if j >= coins[i - 1]:
                    dp[i][j] += dp[i][j - coins[i - 1]]
        return dp[len(coins)][amount]

377. 组合总和 Ⅳ

多重背包(动态规划)实现:

这道题二维dp甚至不能有效的解决问题??尝试了很久至少我设计的状态和转移方程没法合理的进行状态填充。

而一维dp可以非常优雅地解决,目标总和为j,将总和拆解成j - nums[i]分别求排列数再加起来,就是和为j的排列总数。内循环遍历nums[i]就是遍历一个完备事件组来得到和为j(这里体现条件nums[i]互不相同这个条件的重要性,只有nums[i]互不相同,每次使用的nums[i]才独立且唯一)将他们加起来就是和为j的所有排列数。

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp = [0] * (target + 1)
        dp[0] = 1
        for j in range(1, target + 1):
            for i in range(len(nums)):
                if j - nums[i] >= 0:
                    dp[j] += dp[j - nums[i]]
        return dp[target]

带备忘的递归实现:

实际上带备忘的递归实现比较直观:

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        memo = {}

        def dfs(remaining):
            if remaining == 0:
                return 1
            if remaining in memo:
                return memo[remaining]

            count = 0
            for num in nums:
                if remaining - num >= 0:
                    count += dfs(remaining - num)
            
            memo[remaining] = count
            return count

        return dfs(target)

今日总结:

主要纠结的点在第三题,总觉得总和为j - nums[i]有dp[j - nums[i]]种排列方法,加入nums[i]方法数取决于把nums[i]插在什么位置,这样想其实有点跟动态规划的思想背道而驰了。动态规划并不考虑这个问题,只关心达到总和j的排列数量,并且外循环的过程中已经隐式的包含了这个问题,对于任何一个num,考虑外面的大循环,如果每次都加在末尾,其实已经加在所有可能的位置了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值