动态规划背包问题学习笔记:目标和(0-1背包),零钱兑换(完全背包)

学习路线参考:0-1背包 完全背包【基础算法精讲 18】_哔哩哔哩_bilibili

ps:笔记和代码按本人理解整理,重思路

【如果笔记对你有帮助,欢迎关注&点赞&收藏,收到正反馈会加快更新!谢谢支持!】

背包问题:

  • 问题:一个最大容量为 V的背包,能装多少价值的物体 (物体A、B、C…)
  • 物体A重量为 w_A , 价值为v_A;物体B重量为 w_B , 价值为v_B;物体C重量为 w_C , 价值为v_C
  • 0-1背包问题:每个物体只有一个 → 不选 or 选
  • 完全背包问题:每个物体有无数个 → 不选 or 选x个

题目1:目标和

494. 目标和 - 力扣(LeetCode)

  • 题目类型:0-1背包问题(选or不选 → 正 or 负)使结果恰好等于目标和
  • 拆解:
    • 正数的和为 p,负数的和为 q:1. p - q = target     2.  p + q = sum(nums) = s
    • 得到 p = (s + target) / 2  &  q = (s - target) / 2   
    • 问题转化成:和恰好为p 或 q 的数有几组?(下面以找到和为q为例)
    • 比如 nums = [1,1,1,1,1],target = 3 计算得 p = 4, q = 1 → 共5个方案
    • 另:因为pq是整数,所以 (s + target) 和 (s - target) 必须是偶数
  • 转化为动态规划问题:
    • nums 中有几组数,他们的和为 q
    • 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”的方案数}
  • 代码:
    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]

题目2:零钱兑换【HOT100】

322. 零钱兑换 - 力扣(LeetCode)

  • 题目类型:完全背包问题(无数个物体),求满足金额的最少硬币数量
  • 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

往期动态规划学习笔记:

区间DP学习笔记:最长回文子序列,多边形三角剖分的最低得分_最长回文子列表-CSDN博客

状态机DP学习笔记-CSDN博客

树形DP学习笔记(一):树的路径问题-CSDN博客

树形DP学习笔记(二):打家劫舍III & 监控二叉树-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值