【动态规划】力扣322:零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

『 一文搞懂完全背包问题 』从0-1背包到完全背包,逐层深入+推导

方法1:动态规划

  1. 确定dp数组以及下标的含义
    dp[j]:凑足总额为j所需钱币的最少个数为dp[j]
  2. 确定递推公式
    得到dp[j](考虑coins[i]),只有一个来源,dp[j - coins[i]](没有考虑coins[i])。
    凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])
    所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。
    递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
  3. dp数组如何初始化
    首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;
    其他下标对应的数值呢?
    考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。
    所以下标非0的元素都是应该是最大值。
  4. 确定遍历顺序
    本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。所以本题并不强调集合是组合还是排列。
  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
  • 如果求排列数就是外层for遍历背包,内层for循环遍历物品。
    所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包or 外层for遍历背包,内层for循环遍历物品都是可以的!
    本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序.
    综上所述,遍历顺序为:coins(物品)放在外循环,target(背包)在内循环。且内循环正序。
  1. 举例推导dp数组
    以输入:coins = [1, 2, 5], amount = 5为例,dp[amount]为最终结果。
class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        '''版本一'''
        # 初始化
        dp = [amount + 1]*(amount + 1)
        dp[0] = 0
        # 遍历物品
        for coin in coins:
            # 遍历背包
            for j in range(coin, amount + 1):
                dp[j] = min(dp[j], dp[j - coin] + 1)
        return dp[amount] if dp[amount] < amount + 1 else -1

作者:carlsun-2
链接:https://leetcode-cn.com/problems/coin-change/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-wan-q-80r7/

时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 O(S) 个状态,S 为题目所给的总金额。对于每个状态,每次需要枚举 n 个面额来转移状态,所以一共需要 O(Sn) 的时间复杂度。
空间复杂度:O(S)。数组 dp 需要开长度为总金额 S 的空间。

方法2:记忆化搜索

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        @functools.lru_cache(amount)
        def dp(rem) -> int:
            if rem < 0: return -1
            if rem == 0: return 0
            mini = int(1e9)
            for coin in self.coins:
                res = dp(rem - coin)
                if res >= 0 and res < mini:
                    mini = res + 1
            return mini if mini < int(1e9) else -1

        self.coins = coins
        if amount < 1: return 0
        return dp(amount)

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/

时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 S 个状态的答案,且每个状态 F(S) 由于上面的记忆化的措施只计算了一次,而计算一个状态的答案需要枚举 n 个面额值,所以一共需要 O(Sn) 的时间复杂度。
空间复杂度:O(S),我们需要额外开一个长为 S 的数组来存储计算出来的答案 F(S) 。

posted @ 2022-04-25 18:49  Vonos  阅读(105)  评论(0)    收藏  举报