Leetcode刷题笔记44:动态规划6(完全背包理论-518.零钱兑换II-377.组合总和 Ⅳ)

180 阅读5分钟

导语

leetcode刷题笔记记录,本篇博客是动态规划,主要记录题目包括:

知识点

完全背包

在完全背包问题中,我们有一系列物品和一个背包,每种物品都有自己的重量和价值。不同于0-1背包问题,完全背包问题允许多次选择同一种物品。目标通常是选择一些物品,使得背包中物品的总重量不超过给定的限制,同时最大化总价值。

动态规划解法

在动态规划解法中,通常会定义一个数组 dp,其中 dp[i] 代表了当背包容量为 i 时的最大价值。

代码示例(Python):

def complete_knapsack(weight, value, capacity):
    dp = [0] * (capacity + 1)
    for i in range(len(weight)):
        for j in range(weight[i], capacity + 1):
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
    return dp[capacity]

0-1背包问题的动态规划解法

在0-1背包问题中,每种物品也有自己的重量和价值,但每种物品只能选择一次或者不选择。目标和完全背包问题一样:选择一些物品,使得背包中物品的总重量不超过给定的限制,同时最大化总价值。在动态规划解法中,同样会定义一个数组 dp,其中 dp[i] 代表了当背包容量为 i 时的最大价值。

代码示例(Python):

def zero_one_knapsack(weight, value, capacity):
    dp = [0] * (capacity + 1)
    for i in range(len(weight)):
        for j in range(capacity, weight[i] - 1, -1):
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
    return dp[capacity]

区别

  1. 物品选择次数:在完全背包问题中,每种物品可以被选择多次;而在0-1背包问题中,每种物品最多只能被选择一次。

  2. 动态规划方程更新

    • 在完全背包问题中,对于每个物品,动态规划方程从小到大更新(for j in range(weight[i], capacity + 1))。
    • 在0-1背包问题中,对于每个物品,动态规划方程从大到小更新(for j in range(capacity, weight[i] - 1, -1))。
  3. 应用场景:完全背包问题通常适用于物品可以无限次选取的情况,如找零问题、硬币组合等;而0-1背包问题适用于物品只能选或不选的情况。

  4. 时间复杂度:两者的时间复杂度通常都是 O(NC),其中 N 是物品数量,C 是背包容量。但是由于完全背包问题中同一种物品可能会被选择多次,所以实际运行时间可能会有所不同。

Leetcode 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 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000

解法

使用动规五部曲:

  1. dp[j]表示装满容量为j的背包,一共有dp[j]种方法;
  2. 递推公式为:dp[j]+=dp[jcoins[i]]dp[j]+=dp[j-coins[i]](一共多少种方法的问题,一般都是这个公式);
  3. 初始化这里必须dp[0]=1,其他为0
  4. 遍历顺序为先遍历物品、后遍历背包,这样得到的结果为组合数,而如果先遍历背包、后遍历物品,则得到结果为排列数
  5. 打印dp数组
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        # 初始化一个大小为 (amount + 1) 的数组 dp。
        # dp[i] 将存储凑成金额 i 的硬币组合数。
        dp = [0] * (amount + 1)
        
        # 用 0 个硬币凑成金额 0 的组合只有一种,即不选取任何硬币。
        dp[0] = 1
        
        # 遍历所有硬币,用它们更新 dp 数组。
        for i in range(len(coins)):
            # 对于每一个硬币,从其面值开始直到总金额,更新 dp 数组。
            for j in range(coins[i], amount + 1):
                # dp[j] 等于不使用当前硬币的组合数 dp[j] 加上
                # 使用当前硬币的组合数 dp[j - coins[i]]。
                dp[j] += dp[j - coins[i]]
        
        # dp[amount] 存储了凑成总金额的硬币组合数。
        return dp[amount]

Leetcode 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 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

 

进阶: 如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

解法

这道题目实际上就是上一道题目的考虑顺序版本,只需要交换一下for循环的遍历顺序,注意背包的容量target一定要取到!

        # 从 1 到 target 迭代,计算每个可能的目标和
        for j in range(1, target + 1):
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        # 初始化一个长度为 target+1 的数组 dp,并将 dp[0] 设置为 1。
        # dp[i] 将存储和为 i 的元素组合的个数。
        dp = [0] * (target + 1)
        dp[0] = 1  # 唯一和为 0 的组合是不选取任何数字,所以只有一种方式。
        
        # 从 1 到 target 迭代,计算每个可能的目标和
        for j in range(1, target + 1):
            # 遍历每个数 nums[i],检查它是否可以用于构建和为 j 的组合。
            for i in range(len(nums)):
                # 如果当前的目标和 j 大于或等于 nums[i],
                # 那么就可以用 nums[i] 去构建和为 j 的组合。
                if j >= nums[i]:
                    # dp[j] 加上 dp[j - nums[i]],
                    # 因为每一种和为 j - nums[i] 的组合都可以通过添加 nums[i] 来得到一个和为 j 的组合。
                    dp[j] += dp[j - nums[i]]
        
        # 返回和为 target 的组合个数
        return dp[target]

总结

image.png