动态规划 | 完全背包问题 | 组合数、排列数 | leecode刷题笔记

跟随carl代码随想录刷题
语言:python


递推公式:dp[j] += dp[j - nums[i]]

  • 如果求组合数,那外层for循环遍历物品,内层for循环遍历背包。
  • 如果求排列数,那外层for遍历背包,内层for循环遍历物品。

494. 目标和

题目:给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
👉示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
👉示例 2:
输入:nums = [1], target = 1
输出:1

题目分析

思路:
想要使表达结果为target
那么target一定可以表示成left组合 + right组合 = target
因此,公式为:left - (sum - left) = target -> left = (target + sum) / 2
因此,要找出和为left的组合

定义

  • left为加法组合
  • right为减法组合
  • 注意:left必须是整数。即:if target + sum / 2 % 2 == 1: return 0 # 无解
  • 如果if abs(target) > sum: return 0 # 也是没有方案的

因为本题中元素只能用一次,所以也是01背包问题

动态规划五部曲

  1. 确定dp数组以及下标的含义
    • dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
  2. 确定递推公式
    • 求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]]
    • 把所有的dp[j]累加起来
  3. dp数组如何初始化
    • dp[0]一定要初始化为1【不能初始化为0!因为dp[0]是一切推导的起点,如果dp[0]=0,那么后续递推结果都是0】
  4. 确定遍历顺序
    • nums放在外循环
    • target放在内循环
  5. 举例推导dp数组
    • 输入nums[1, 1, 1, 1, 1], target = 3
    • bag_size = (target + sum)/2 = (3+5)/2 = 4
    • dp数组的状态变化如下:
    • ……

完整代码如下

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        sum_ = sum(nums)
        # 如果目标值比总和大,或者 (sum_ + target) % 2 为奇数,那么就不可能存在
        if abs(target) > sum_ or (sum_ + target) % 2 == 1: return 0

        bag_size = (sum_ + target) // 2

        # 初始化dp
        dp = [0] * (bag_size + 1)
        dp[0] = 1

        # 遍历
        for i in range(len(nums)):
            for j in range(bag_size, nums[i] - 1, -1):
                dp[j] += dp[j - nums[i]]
        return dp[bag_size]

518. 零钱兑换 II

题目:给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个
题目数据保证结果符合 32 位带符号整数。
👉示例 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
👉示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
👉示例 3:
输入:amount = 10, coins = [10]
输出:1

题目分析——组合

组合不强调元素之间的顺序,排列强调元素之间的顺序。
本题是求组合数。

动态规划五部曲

  1. dp的含义
    • dp[i]表示:凑成金额为i的组合数为dp[i-1]
  2. 确定递推公式
    • 组合数的递推公式不涉及+value
    • dp[j] += dp[j - coins[i]]
  3. dp数组如何初始化
    • dp[0]一定要初始化为1!!!dp[0]是后续推导的起点,如果dp[0]=0,那么后续推导的结果都是0。
    • dp = [0] * (amount + 1)dp[0] = 1
  4. 确定遍历顺序
    • 本题需要考虑顺序
    • 求组合数:先遍历物品,再遍历背包
    • 求排列数:先遍历背包,再遍历物品
  5. 举例推导dp数组

完整代码如下

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        # 初始化
        dp = [0] * (amount + 1)
        dp[0] = 1  # 凑成金额为0的货币组合数是1
        # 遍历
        for i in range(len(coins)):
            for j in range(coins[i], amount + 1):
                dp[j] += dp[j - coins[i]]
        # print(dp)
        return dp[amount]

377. 中等组合总和 Ⅳ

题目:给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数
题目数据保证答案符合 32 位整数范围。
👉示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
👉示例 2:
输入:nums = [9], target = 3
输出:0

题目分析——排列总和数

  • 给定元素是无限个可用,即:可以重复使用。
  • 给定元素不一定全部使用,即:可以只使用一部分
  • (1, 1, 2)和(1, 2, 1)是不同的答案,即:本题求的是排列数,而非组合数。答案中元素是有序的。组合是无序的,排列是有序的。

动态规划五部曲

  1. dp[i]:表示总和为i排列个数为dp[i]
  2. dp[i] += dp[i - nums[j]]
  3. 初始化:
    • dp = [0] * (target + 1) # 非0坐标要初始化为0,防止在后续遍历的时候覆盖dp的值。
    • dp[0] = 1 # dp[0]初始化为1是没有意义的。然而其必须初始化为1!如果初始化为0,那么后续的推导就都为0了。
  4. 遍历顺序
    • 如果求组合数,那外层for循环遍历物品,内层for循环遍历背包。
    • 如果求排列数,那外层for遍历背包,内层for循环遍历物品。
    • 本题是求排列数,因此:
      • 外层遍历总和数(即:背包容量)for j in range(target):
      • 内层是遍历元素for i in range(len(nums)):
  5. 举例推导dp数组

请添加图片描述

完整代码如下

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        # 初始化
        dp = [0] * (target + 1)
        dp[0] = 1

        # 遍历
        for i in range(1, target + 1):
            for j in nums:
                if i >= j:  # 保证索引不为负
                    dp[i] += dp[i - j]
        return dp[target]

70. 爬楼梯

题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
👉示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

👉示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

题目分析——排列总和数

需要n阶到达楼顶:背包容量是n
每次你可以爬 1 或 2 个台阶。:物品为nums = [1, 2]
依旧是求排列数

动态规划五部曲

  1. dp[i]:表示爬n阶台阶共有dp[i]种方法
  2. dp[i] += dp[i - nums[j]]
  3. 初始化:dp = [0] * (n + 1)dp[0] = 1
  4. 遍历顺序:因为是排列,所以外层遍历背包,内层遍历物品
    • for i in range(1, n + 1): # 注意:这里很重要,不是range(n),而是range(1, n + 1)
    • for j in nums: if i >= j: …
  5. 举例说明dp[i]
    在这里插入图片描述

完整代码如下

class Solution:
    def climbStairs(self, n: int) -> int:
        nums = [1, 2]

        # 初始化
        dp = [0] * (n + 1)
        dp[0] = 1

        # 遍历
        for i in range(1, n + 1):
            for j in nums:
                if i >= j:
                    dp[i] += dp[i - j]
        # print(dp)
        return dp[n]

每次可以爬(1,m)阶,有几种方式?

class Solution:
    def climbStairs(self, n: int) -> int:
        # 初始化
        dp = [0] * (n + 1)
        dp[0] = 1

        # 遍历
        for i in range(1, n + 1):
            for step in range(1, m + 1):
                if i >= step:
                    dp[i] += dp[i - step]
        # print(dp)
        return dp[n]
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值