Day44代码随想录动态规划part05:完全背包理论、518.零钱兑换II、377. 组合总和 Ⅳ

本文详细讲解了完全背包问题的动态规划解决方案,区分了与01背包的区别,重点讨论了物品和背包容量的遍历顺序对结果的影响,并通过实例分析了如何正确计算组合数和排列数。
摘要由CSDN通过智能技术生成

Day44 动态规划part06 完全背包

完全背包定义:有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

01背包完全背包
1维dp内层遍历顺序内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。物品是可以添加多次的,所以要从小到大去遍历
1维dp外层遍历顺序先遍历物品,再遍历背包对于纯完全背包问题,其for循环的先后循环是可以颠倒的!先遍历物品或者背包都可以,因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。两个for循环的先后循序,都不影响计算dp[j]所需要的值

卡码网题目链接:52. 携带研究材料(第七期模拟笔试) (kamacoder.com)

n,v = map(int, input().split())
# print(n, v)
weight = []
value = []
for i in range(n):
    wi, vi = map(int, input().split())
    weight.append(wi)
    value.append(vi)

dp = [0] * (v+1)

for i in range(n):
    for j in range(v+1):
        if weight[i] <= j:
            dp[j] = max(dp[j], dp[j-weight[i]]+value[i])
    # print(dp)
print(dp[v])

518.零钱兑换II

leetcode题目链接:518. 零钱兑换 II - 力扣(LeetCode)

题意:给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

  • 输入: amount = 5, coins = [1, 2, 5]
  • 输出: 4

解释: 有四种方式可以凑成总金额:

  • 5=5
  • 5=2+2+1
  • 5=2+1+1+1
  • 5=1+1+1+1+1

思路:本题和纯完全背包不一样,纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数!组合不强调元素之间的顺序,排列强调元素之间的顺序

  • dp数组:dp[j]为总数j的有多少种组合方式

  • 推导公式:dp[j] += dp[j-coins[i]],这其中5可以有5+0种,4+1, 3+2,类似01背包题目的时候494. 目标和

  • 初始化:dp[0] = 1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。这里如果我们求的amount为0后台默认组合数为1;下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j];dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。

  • 遍历顺序:**本题很重要!**因为纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序没关系,即:有顺序也行,没有顺序也行!

    那么本题,两个for循环的先后顺序可就有说法了。

    我们先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。代码如下:

    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
            dp[j] += dp[j - coins[i]];
        }}
    

    假设:coins[0] = 1,coins[1] = 5。

    那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。所以这种遍历顺序中dp[j]里计算的是组合数!

    如果把两个for交换顺序,代码如下:

    for (int j = 0; j <= amount; j++) { // 遍历背包容量
        for (int i = 0; i < coins.size(); i++) { // 遍历物品
            if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
        }}
    

    背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。此时dp[j]里算出来的就是排列数!

    如果求组合数就是外层for循环遍历物品,内层for遍历背包

    如果求排列数就是外层for遍历背包,内层for循环遍历物品

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = [0] * (amount+1)
        dp[0] = 1
        '''先物品再背包'''
        for i in range(len(coins)):
            for j in range(coins[i], amount+1):
                dp[j] += dp[j-coins[i]]
            # print(i, dp)
        '''
        0 [1, 1, 1, 1, 1, 1]
        1 [1, 1, 2, 2, 3, 3]
        2 [1, 1, 2, 2, 3, 4]
        '''
        # '''先背包再物品'''
        # for j in range(amount+1):
        #     for i in range(len(coins)):
        #         if j >= coins[i]:
        #             dp[j] += dp[j-coins[i]]
								      # print('j:',j, 'coins[i]:',coins[i], 'j-coins[i]', j-coins[i])
        #     print(j, dp)
        '''
        0 [1, 0, 0, 0, 0, 0]
        1 [1, 1, 0, 0, 0, 0]
        2 [1, 1, 2, 0, 0, 0]
        3 [1, 1, 2, 3, 0, 0]
        4 [1, 1, 2, 3, 5, 0]
        5 [1, 1, 2, 3, 5, 9]
        '''
        return dp[-1]

打印出来再一下为什么后一种遍历顺序不对:可以看到j=3时是1+2相加了一次,后面2 + 1也有一次,计算中有重复,所以这是排列的方法

0 [1, 0, 0, 0, 0, 0]
j: 1 coins[i]: 1 j-coins[i] 0
1 [1, 1, 0, 0, 0, 0]
j: 2 coins[i]: 1 j-coins[i] 1
j: 2 coins[i]: 2 j-coins[i] 0
2 [1, 1, 2, 0, 0, 0]
j: 3 coins[i]: 1 j-coins[i] 2
j: 3 coins[i]: 2 j-coins[i] 1
3 [1, 1, 2, 3, 0, 0]
j: 4 coins[i]: 1 j-coins[i] 3
j: 4 coins[i]: 2 j-coins[i] 2
4 [1, 1, 2, 3, 5, 0]
j: 5 coins[i]: 1 j-coins[i] 4
j: 5 coins[i]: 2 j-coins[i] 3
j: 5 coins[i]: 5 j-coins[i] 0
5 [1, 1, 2, 3, 5, 9]

377. 组合总和 Ⅳ

leetcode题目:. - 力扣(LeetCode)

题意:给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

  • nums = [1, 2, 3]
  • target = 4

所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)请注意,顺序不同的序列被视作不同的组合。因此输出为 7。

思路:本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值