动态规划背包问题刷题笔记:和为目标值的最长子序列的长度,分割等和子集,将一个数字表示成幂的和的方案数,完全平方数

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

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

上期笔记:

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

背包问题:

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

题目1:和为目标值的最长子序列的长度

2915. 和为目标值的最长子序列的长度 - 力扣(LeetCode)

  • 题意:        
    • 条件:子序列的和为目标和
    • 求:子序列 长度的最大值 
  • 类型:0-1背包问题(每个数只有选 or 不选)
  • 拆解:
    • 0-1背包问题,先遍历物体,再遍历总额
    • dp 储存当前子序列长度,初始化为 -inf (因为要求最大值),dp[0] = 0(满足和为0的子序列长度为0)
    • 一维dp更新需要从右往左(此时左边的值还是上轮num对应的长度):
      • dp[c] = max(dp[c], dp[c-num]+1) = max({不选num且和为c的子序列长度}, {和为c-num的子序列长度 + 1(指num)})
  • 代码:
    class Solution:
        def lengthOfLongestSubsequence(self, nums: List[int], target: int) -> int:
            n = len(nums)
            dp = [float('-inf')]*(target+1)
            dp[0] = 0
    
            for num in nums:
                for c in range(target, num-1, -1):  # 注意 c >= num
                    dp[c] = max(dp[c], dp[c-num]+1)
            return dp[-1] if dp[-1] != float('-inf') else -1

题目2:分割等和子集

416. 分割等和子集 - 力扣(LeetCode)

  • 题意:        
    • 条件:子集和 = sum(nums) / 2 
    • 求:是否存在满足条件的子集
  • 类型:0-1背包问题(每个数只有选 or 不选)
  • 拆解:
    • target = sum(nums) / 2 ,如果sum(nums)为奇数,target有小数,肯定不存在能平分的子集
    • dp 储存是否找到已找到子集(True or False,用1 or 0表示)
    • 初始化为 0 (指False),dp[0] = 1 (存在和为0的子集,即空集)
    • 从右往左更新新的num:
      • 如果满足 dp[c-num] == 1 (存在和为c-num的子集),则 dp[c] = 1 (和为c的子集也存在)
      • 否则,就保持原来的dp[c],即 {不选num是否存在和为c的子集}
  • 代码:
    class Solution:
        def canPartition(self, nums: List[int]) -> bool:
            sum_ = sum(nums)
            if sum_ % 2 == 1:
                return False
    
            target = sum_ / 2
            dp = [0 for _ in range(target+1)]
            dp[0] = 1
            for i, num in enumerate(nums):
                for c in range(target, num-1, -1):
                    if dp[c-num] == 1:  # 看是否存在,dp[c-num]为True,dp[c]也为True
                        dp[c] = 1
            return dp[-1] == 1

题目3:将一个数字表示成幂的和的方案数

2787. 将一个数字表示成幂的和的方案数 - 力扣(LeetCode)

  • 题意:        
    • 条件: 互不相同 正整数的 x 次幂之和 = n
    • 求:方案数
  • 类型:0-1背包问题(互不相同:每个数只有选 or 不选)
  • 拆解:
    • 处理nums = [ i**x for i in range(n+1)]  并且 i**x < n
    • 方案数 = {不选 num 的方案数} + {选 num 的方案数}  = dp[c] + dp[c-num]
  • 代码:
    class Solution:
        def numberOfWays(self, n: int, x: int) -> int:
            nums = []
            for i in range(1, n+1):
                if i**x > n:
                    break
                nums.append(i**x)
            
            dp = [0]*(n+1)
            dp[0] = 1
            for num in nums:
                for c in range(n, num-1, -1):
                    dp[c] += dp[c-num]
            return dp[-1] % (10**9 + 7)

题目4:零钱兑换 II

518. 零钱兑换 II - 力扣(LeetCode)

  • 题意:        
    • 条件:硬币(无限多)凑成总金额
    • 求:方案数
  • 类型:完全背包问题(每个硬币选 n 个, n=0,1,2,…)
  • 拆解:
    • 完全背包但是要:先遍历硬币,再遍历金额 → 防止重复计算(比如 amount=3, "1, 2"和"2, 1" 是同一个方案,不能重复计数)
    • 方案数 = {不选 num 的方案数} + {选 num 的方案数}  = dp[c] + dp[c-num]
    • 本题需要正序遍历金额,因为硬币可以多次用:
      • 如 amount = 5, coin = 1
        012345
        [idx = 0] 初始 100000
        [idx = 1]  coin = 1=dp[0] =1=dp[1] + dp[1-1] = 0+1= 1=dp[2] + dp[2-1] = 0+1= 1=dp[3] + dp[3-1] = 0+1= 1=dp[4] + dp[4-1] = 0+1= 1=dp[5] + dp[5-1] = 0+1= 1
  • 代码:

    class Solution:
        def change(self, amount: int, coins: List[int]) -> int:
            dp = [0 for _ in range(amount+1)]
            dp[0] = 1
    
            for coin in coins:
                for c in range(coin, amount+1): # 正序
                    dp[c] += dp[c-coin]
            return dp[-1]

题目5:完全平方数

279. 完全平方数 - 力扣(LeetCode)

  • 题意:        
    • 条件:完全平方数(可重复)和为n
    • 求:最少数量
  • 类型:完全背包问题
  • 拆解:
    • “先遍历平方数,再遍历总额” / “先遍历总额,再遍历平方数” 都可以,因为是求最小值,所以同一组合重复计算不影响结果
    • 以“先遍历平方数,再遍历总额”为例
    • 初始化为 inf(因为要求最小值),dp[0] = 0(总额为0需要的平方数的数量为0)
    • 正序计算(因为平方数可以被重复使用)dp[c] = min(dp[c], dp[c-sq]+1) = {不选 sq 的平方和数量} + {选 sq 的平方和数量} 
  • 代码:
    class Solution:
        def numSquares(self, n: int) -> int:
            # 计算平方和
            s = 1
            squres = []
            while s**2 <= n:
                squres.append(s**2)
                s += 1
            # 开始动态规划
            dp = [float('inf')]*(n+1)
            dp[0] = 0
            
            for sq in squres:
                for c in range(sq, n+1): # 正序(可重复取sq)
                    dp[c] = min(dp[c], dp[c-sq]+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、付费专栏及课程。

余额充值