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]