文档讲解:代码随想录
视频讲解:代码随想录B站账号
状态:看了视频题解和文章解析后做出来了
完全背包
class Solution:
def completeBag(self, weight, value, bagweight):
dp = [0] * (bagweight + 1)
# 遍历物品,从0开始
for i in range(len(weight)):
# 物品重复使用,但从背包重量等于物品重量开始遍历,因为小于是装不进去的
for j in range(weight[i], bagweight + 1):
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
return dp[bagweight]
- 时间复杂度:O(n^2)
- 空间复杂度:O(n)
完全背包,和01背包唯一的区别就是每件物品可以重复无限次放入背包中。
动态规划五部曲:
1. 确定dp数组以及下标的含义:dp[j]代表背包重量为j时可以放入的最大价值
2. 确定递推公式:
完全背包的递推公式同样为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
3. dp数组初始化:
dp全部初始化为0,因为遍历途中有max和相加的操作,所以赋值为非负数的最小整数0.
如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
4. 确定遍历顺序:
在这道题中,遍历顺序其实无所谓。先遍历背包还是先遍历物品都行,遍历背包从前往后和从后往前也都可以。
- 为什么有些题只能先遍历物品?
一维的解决01背包的时候,如果先从后往前遍历背包,那么每个背包只会被放入一件物品。因为每个背包只被总体循环遍历过一次。
- 为什么有些题需要背包从后往前遍历?
一维解决01背包的时候,如果从前往后遍历,那么每个物品就可以使用多次了。
5. 举例推导dp数组
518. 零钱兑换 II
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]]
return dp[amount]
完全背包问题,代码思路和494.目标和完全一样,这里不再赘述了。
377. 组合总和 Ⅳ
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
dp = [0] * (target + 1)
for i in range(1, target+1):
for n in nums:
if n == i:
dp[i] += 1
elif n < i:
dp[i] += dp[i-n]
return dp[-1]
五部曲也类似与完全背包问题,但这道题要求结果是排列的数量。排列就意味着顺序也必须考虑在内,[1, 2] 和 [2, 1]是两个不同的排列方法。
这时候我们的遍历顺序就必须先从背包开始遍历,再从物品遍历。这点要解释起来非常抽象,通过打印两种方法的dp数组更新逻辑来看:
第二种是先背包再物品,可以看到第五个数组[1,1,2,0,0,0],这里的2其实包含两种置放方法,一种是[1,1],一种是[2]。再到第七个数组[1,1,2,2,0,0],第二个2其实包含了[1,1,1]和[2,1]两种方法。第八个数组的3(也就是多的那个1)包含[1,2]这个组合。
这样[1,2]和[2,1]两种排列就都计入其中了。
从前往后遍历还是因为是完全背包问题,元素可以重复使用。