🥈Medium

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

示例 1

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

示例 2

  1. 输入:coins = [2], amount = 3
  2. 输出:-1

示例 3

  1. 输入:coins = [1], amount = 0
  2. 输出:0

示例 4

  1. 输入:coins = [1], amount = 1
  2. 输出:1

示例 5

  1. 输入:coins = [1], amount = 2
  2. 输出:2

提示

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 🥈322.零钱兑换@ - 图1
  • 0 <= amount <= 🥈322.零钱兑换@ - 图2

    题解

    还是不大会,😔,需要好好领会!!

    暴力递归

    首先,这是一个动态规划问题。因为它具有最优子结构。要符合最优子结构,子问题之间必须相互独立。例如,要求amount=11时最少硬币数(原问题),如果知道amount=10的最少硬币数(子问题),只需要把子问题的答案+1(选出一枚面值为1的硬币),就是原问题的答案。因为硬币个数没有限制,所以子问题之间相互独立。

既然知道这是一个动态规划,就要列出正确的动态转移方程

1.确定base case

很简单,amount=0时,返回0

2.确定状态,也就是原问题和子问题中的变量

由于硬币是无限的,面额是题目给定的,只有目标金额会不断向base case靠近,所以唯一状态就是目标金额amount

3.确定选择,也就是导致状态产生变化的行为

每选择一枚硬币,就减少了目标金额,所以选择就是硬币的面额

4.明确dp函数/数组的定义

很明显,这里的dp函数,需要输入目标金额n,返回凑出目标金额n的最少硬币数。

这样就可以写出大致框架:

根据上面这些关键点,再加上base case,代码就可以写出来了:

  1. class Solution:
  2. def coinChange(self, coins: List[int], amount: int) -> int:
  3. # 定义:要凑出金额n,至少需要dp(n)枚硬币
  4. def dp(n):
  5. # base case
  6. if n == 0:
  7. return 0
  8. if n < 0:
  9. return -1
  10. res = float('INF')
  11. for coin in coins:
  12. subproblem = dp(n - coin)
  13. # 子问题无解,跳过
  14. if subproblem == -1:continue
  15. # 选择需要硬币数最少的结果
  16. res = min(res, 1 + subproblem)
  17. return res if res != float('INF') else -1
  18. return dp(amount)

但这样时间复杂度很高,提交也会显示超出时间限制
image.png
不过可以根据上面的过程写出状态转移方程:

🥈322.零钱兑换@ - 图4

画出amount=11,``coins=[1,2,5]的递归树如图:

image.png

带备忘录的递归

  1. class Solution:
  2. def coinChange(self, coins: List[int], amount: int) -> int:
  3. # 定义:要凑出金额n,至少需要dp(n)枚硬币
  4. memo = dict()
  5. def dp(n):
  6. # 查备忘录
  7. if n in memo:
  8. return memo[n]
  9. # base case
  10. if n == 0:
  11. return 0
  12. if n < 0:
  13. return -1
  14. res = float('INF')
  15. for coin in coins:
  16. subproblem = dp(n - coin)
  17. # 子问题无解,跳过
  18. if subproblem == -1:continue
  19. # 选择需要硬币数最少的结果
  20. res = min(res, 1 + subproblem)
  21. memo[n] = res if res != float('INF') else -1
  22. return memo[n]
  23. return dp(amount)

dp数组的迭代