问题描述
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
Leetcode 322 零钱兑换: https://leetcode-cn.com/problems/coin-change/.
解题思路
1. 初级动态规划——不带存储单元的递归
分析:该问题具有最优子结构。因为如果要求amount = n的最少硬币数(原问题),可以转化为amount = n-1(子问题)再加上1(一个一块的硬币),这就是原问题的答案。因为硬币的数量没有限制,子问题之间没有相互制约的,是独立的。这就是典型的动态规划问题。
状态转移方程的确认
- 状态:原问题和子问题中变化的变量,由于硬币数量无限,唯一变化的就是amount。
- 状态转移方程:对于amount,至少需要dp(n)个硬币凑成该金额。那么状态是如何转化的呢?目标金额会随着在coin中选一个硬币变小的。直到目标金额变成0了,所需要的硬币数量就是0。目标金额小于0,无解,返回-1。
写成数学形式如下:
d p ( n ) = { 0 , n = 0 − 1 , n < 0 min { d p ( n − coin ) + 1 ∣ coin ∈ coins } , n > 0 d p(n)=\left\{\begin{array}{l} 0, n=0 \\ -1, n<0 \\ \min \{d p(n-\operatorname{coin})+1 \mid \operatorname{coin} \in \operatorname{coins}\}, n>0 \end{array}\right. dp(n)=⎩⎨⎧0,n=0−1,n<0min{dp(n−coin)+1∣coin∈coins},n>0
def coinChange(self, coins: List[int], amount: int) -> int:
def dp(n):
if n == 0:
return 0
if n < 0:
return -1
# 记下最小值
minVal = float('inf')
# 分别算coin,构造递归树
for coin in coins:
subproblem = dp(n-coin)
# 等于-1说明 n-coin 小于0了,不能兑换,就continue
if subproblem == -1:
continue
# 取硬币的最小次数
minVal = min(minVal,subproblem+1)
if minVal != float('inf'):
return minVal
else:
return -1
return dp(amount)
总结:哇哦,超出时间限制了!时间复杂度为:递归树节点个数。O(N**K)。
2.带存储列表的递归
用字典存储n时的硬币最小次数,避免重复计算。
def coinChange(self, coins: List[int], amount: int) -> int:
memo = dict()
def dp(n):
if n in memo:
return memo[n]
if n == 0:
return 0
if n < 0:
return -1
minVal = float('inf')
for coin in coins:
subproblem = dp(n-coin)
if subproblem == -1:
continue
minVal = min(minVal,subproblem+1)
if minVal != float('inf'):
memo[n] = minVal
else:
memo[n] = -1
return memo[n]
return dp(amount)
总结:时间复杂度:O(n)
3. 用动态转移矩阵
采用自底向上的方式
d p [ i ] = x dp[i]=x dp[i]=x表示目标金额为 i i i时,至少需要x块硬币
def coinChange(self, coins: List[int], amount: int) -> int:
memo = [0] + [float('inf')] * amount
for i in range(amount+1):
for coin in coins:
if i >= coin:
memo[i] = min(memo[i],memo[i - coin]+1)
if memo[amount] <= amount:
return memo[amount]
else:
return -1
references
[1] Leetcode 322 零钱兑换: https://leetcode-cn.com/problems/coin-change/.
[2] labuladong的算法小抄: https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie#er-cou-ling-qian-wen-ti.