题目内容
小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。
例如:小C有三种硬币,面值分别为 1, 2, 5。他需要凑出总金额为 18。一种最优的方案是使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。
题目分析
问题理解
我们需要找到一种方法,使用最少数量的硬币凑出给定的总金额。这个问题可以通过动态规划来解决。动态规划的核心思想是将问题分解为子问题,并存储子问题的解,以避免重复计算。
数据结构选择
我们使用一个一维数组 dp 来存储每个金额所需的最少硬币数量。dp[i] 表示凑出金额 i 所需的最少硬币数量。
算法步骤
-
初始化:
- 创建一个长度为
amount + 1的数组dp,并将所有元素初始化为float('inf'),表示初始状态下每个金额所需的硬币数量为无穷大。 - 设置
dp[0] = 0,因为凑出金额为 0 时所需的硬币数量为 0。
- 创建一个长度为
-
动态规划:
- 遍历每种硬币面值
coin。 - 对于每个硬币面值
coin,遍历从coin到amount的所有金额i。 - 更新
dp[i]为min(dp[i], dp[i - coin] + 1),表示使用当前硬币面值时,凑出金额i所需的最少硬币数量。
- 遍历每种硬币面值
-
回溯:
- 如果
dp[amount]仍然是float('inf'),表示无法凑出目标金额,返回空列表。 - 否则,从
amount开始回溯,确定使用的硬币组合。 - 遍历每种硬币面值,找到使得
dp[i] == dp[i - coin] + 1的硬币,并将其加入结果列表。
- 如果
-
排序:
- 对结果列表进行排序,使其从大到小排列。
具体例子
例子1
输入:coins = [1, 2, 5], amount = 18
步骤:
-
初始化
dp数组:dp = [0, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf] -
使用硬币面值
1:dp = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
-
使用硬币面值
2:dp = [0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
-
使用硬币面值
5:dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 3, 3, 4, 4, 3, 4, 4, 5]
-
回溯:
dp[18] = 5,表示需要 5 个硬币。- 回溯路径:
18 -> 13 -> 8 -> 3 -> 1 -> 0,使用的硬币为[5, 5, 5, 2, 1]。
-
排序:
[5, 5, 5, 2, 1]
输出:[5, 5, 5, 2, 1]
例子2
输入:coins = [1, 3, 4], amount = 6
步骤:
-
初始化
dp数组:dp = [0, inf, inf, inf, inf, inf, inf] -
使用硬币面值
1:dp = [0, 1, 2, 3, 4, 5, 6]
-
使用硬币面值
3:dp = [0, 1, 2, 1, 2, 3, 2]
-
使用硬币面值
4:dp = [0, 1, 2, 1, 1, 2, 2]
-
回溯:
dp[6] = 2,表示需要 2 个硬币。- 回溯路径:
6 -> 3 -> 0,使用的硬币为[3, 3]。
-
排序:
[3, 3]
输出:[3, 3]
例子3
输入:coins = [5], amount = 10
步骤:
-
初始化
dp数组:dp = [0, inf, inf, inf, inf, 1, inf, inf, inf, inf, 2] -
使用硬币面值
5:dp = [0, inf, inf, inf, inf, 1, inf, inf, inf, inf, 2]
-
回溯:
dp[10] = 2,表示需要 2 个硬币。- 回溯路径:
10 -> 5 -> 0,使用的硬币为[5, 5]。
-
排序:
[5, 5]
输出:[5, 5]
参考代码
def solution(coins, amount):
dp = [float('inf')] * (amount + 1) # 初始化 dp 列表
dp[0] = 0 # 0 总额需要 0 个硬币
for coin in coins: # 遍历每种硬币面值
for i in range(coin, amount + 1): # 完全背包计算
dp[i] = min(dp[i], dp[i - coin] + 1) # 更新 dp 列表
if dp[amount] == float('inf'): # 如果无法达到目标总额
return []
result = []
i = amount
while i > 0: # 回溯确定使用的硬币
for coin in coins:
if i >= coin and dp[i] == dp[i - coin] + 1:
result.append(coin)
i -= coin
break
result.sort(reverse=True)
return result
总结
通过动态规划和回溯的方法,我们可以有效地找到使用最少数量的硬币凑出给定总金额的方案。动态规划部分用于计算每个金额所需的最少硬币数量,回溯部分用于确定具体的硬币组合。最后,对结果列表进行排序,使其从大到小排列。