学习路线参考:0-1背包 完全背包【基础算法精讲 18】_哔哩哔哩_bilibili
ps:笔记和代码按本人理解整理,重思路
【如果笔记对你有帮助,欢迎关注&点赞&收藏,收到正反馈会加快更新!谢谢支持!】
背包问题:
- 问题:一个最大容量为
的背包,能装多少价值的物体 (物体A、B、C…)
- 物体A重量为
, 价值为
;物体B重量为
, 价值为
;物体C重量为
, 价值为
- 0-1背包问题:每个物体只有一个 → 不选 or 选
- 完全背包问题:每个物体有无数个 → 不选 or 选x个
题目1:目标和
- 题目类型:0-1背包问题(选or不选 → 正 or 负)使结果恰好等于目标和
- 拆解:
- 正数的和为
,负数的和为
:1.
2.
- 得到
&
- 问题转化成:和恰好为
或
的数有几组?(下面以找到和为
为例)
- 比如 nums = [1,1,1,1,1],target = 3 计算得 p = 4, q = 1 → 共5个方案
- 另:因为
,
是整数,所以 (s + target) 和 (s - target) 必须是偶数
- 正数的和为
- 转化为动态规划问题:
- nums 中有几组数,他们的和为
- dp[i][c] 表示 “第0到第 i 个num都可以选的情况下,目标和为c” 有几种方案
- 初始化 dp[0][0] = 1,表示“没有num,和为0” 有一种方案
- 因为dp[1]对应第 0 个num,dp[i+1] 对应第 i 个num
- 如果第 i 个num大于 目标和c,不能选第 i 个num,{当前方案数} = {“第0到第 i-1 的num都可以选的情况下,目标和为c”的方案数}【 dp[i+1][c] = dp[i][c] 】
- 如果第 i 个物体重量小于等于 目标和c,选or不选第 i 个物体都可以,{当前方案数} = {不选第 i 个物体的方案数} + {选第 i 个物体的方案数} 【 dp[i+1][c] = dp[i][c] + dp[i][c-nums[i]] 】
- 其中 {选第 i 个物体的方案数} = {“第1到第 i-1 的num都可以选的情况下,目标和为c-nums[i-1]”的方案数
- 其中 {不选第 i 个物体的方案数} = {“第0到第 i-1 的num都可以选的情况下,目标和为c”的方案数}
- nums 中有几组数,他们的和为
- 代码:
class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: s = sum(nums) - abs(target) if s < 0 or s % 2: # 不可能存在目标和的情况:1. target比sum(nums)大 2. sum(nums) - abs(target) 为奇数 return 0 q = s // 2 # 背包容量 n = len(nums) dp = [[0] * (q + 1) for _ in range(n + 1)] dp[0][0] = 1 for i, x in enumerate(nums): for c in range(q + 1): if c < x: # 目标和 < 当前num,不能选当前num dp[i + 1][c] = dp[i][c] # dp[i + 1]对应第i个num else: dp[i + 1][c] = dp[i][c] + dp[i][c - x] # 不选 + 选 return dp[n][q]
- 空间优化:一维dp
- 滚动更新一维dp:
- 如果第 i 个num大于 目标和c:不更新
- 如果第 i 个物体重量小于等于 目标和c:{当前方案数} = dp[c]{不选第 i 个物体的方案数} + dp[c - num]{选第 i 个物体的方案数}
- 因为更新dp时,加上的dp[c - num],要保证是上轮的,所以需要从右边开始更新(如果从左边更新,dp[c - num] 会被本轮的值覆盖,导致错误)
- 代码:
class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: s = sum(nums) - abs(target) if s < 0 or s % 2: return 0 q = s // 2 n = len(nums) dp = [0] * (q + 1) # 一维dp dp[0] = 1 for num in nums: for c in range(q, num-1, -1): # 从右到左更新 dp[c] += dp[c - num] # 因为range中已经确定下 c >= num return dp[-1]
- 滚动更新一维dp:
题目2:零钱兑换【HOT100】
- 题目类型:完全背包问题(无数个物体),求满足金额的最少硬币数量
- 0-1背包问题 和 完全背包问题 解法的区别
- 0-1背包:先遍历物体,再遍历总额
(因为每个物体最多只能选一个,这样确保一个总额不会出现重复的物体) - 完全背包:先遍历总额,再遍历物体
(因为每个物体可多选)
- 0-1背包:先遍历物体,再遍历总额
- 本题求最小,所以dp初始为inf,dp用来记需要的硬币总数
- 代码:
class Solution: def coinChange(self, coins: List[int], amount: int) -> int: dp = [float('inf')]*(amount+1) dp[0] = 0 # 0枚硬币 for num in range(1, amount+1): # 遍历金额 for coin in coins: # 遍历硬币(物体) if coin <= num: dp[num] = min(dp[num], dp[num-coin]+1) return dp[-1] if dp[-1] != float('inf') else -1