问题描述 小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。 例如:小C有三种硬币,面值分别为 1, 2, 5。他需要凑出总金额为 18。一种最优的方案是使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。 测试样例 样例1:
输入:coins = [1, 2, 5], amount = 18 输出:[5, 5, 5, 2, 1]
样例2:
输入:coins = [1, 3, 4], amount = 6 输出:[3, 3]
样例3:
输入:coins = [5], amount = 10 输出:[5, 5]
问题分析:
给定不同面值的硬币,面值数组 coins 和目标金额 amount。我们需要通过最少数量的硬币组合来凑出 amount。
解题思路:
-
动态规划(DP) :
- 定义一个数组
dp,其中dp[i]表示凑出金额i所需的最小硬币数。 - 初始时,
dp[0] = 0,表示凑出金额为0时不需要任何硬币。 - 对于其他的金额
i,我们初始化dp[i]为一个较大的值,表示暂时无法凑出该金额。 - 对每个硬币面值
coin,我们遍历所有金额i(从coin到amount),更新dp[i]的值为dp[i - coin] + 1,表示使用当前硬币后凑出金额i的最小硬币数。
- 定义一个数组
-
找出硬币组合:
- 在计算出
dp[amount]的最小硬币数后,我们还需要回溯硬币组合,找出具体使用哪些硬币来构成目标金额。
- 在计算出
具体步骤:
- 初始化一个大小为
amount + 1的dp数组,初值为一个较大的数(如float('inf')),表示一开始不能凑出任何金额。设置dp[0] = 0,因为凑出金额为 0 不需要硬币。 - 遍历每个硬币,更新
dp数组,记录出使用最少硬币组合出各个金额。 - 最后,如果
dp[amount]仍然是无穷大,说明无法凑出该金额,否则就可以从dp数组中找到最少的硬币数。 - 然后,从
dp数组回溯出具体的硬币组合。
代码实现:
python
def coinChange(coins, amount):
# Step 1: DP initialization
dp = [float('inf')] * (amount + 1) # dp[i] represents the minimum coins for amount i
dp[0] = 0 # No coins needed to make amount 0
# Step 2: Fill dp array using the coin values
for coin in coins:
for i in range(coin, amount + 1):
dp[i] = min(dp[i], dp[i - coin] + 1)
# Step 3: If dp[amount] is still infinity, it means no solution
if dp[amount] == float('inf'):
return [] # No solution
# Step 4: Backtrack to find the exact coins used
result = []
current_amount = amount
while current_amount > 0:
for coin in coins:
if current_amount >= coin and dp[current_amount] == dp[current_amount - coin] + 1:
result.append(coin)
current_amount -= coin
break
return result
# 示例:
coins1 = [1, 2, 5]
amount1 = 18
print(coinChange(coins1, amount1)) # 输出:[5, 5, 5, 2, 1]
coins2 = [1, 3, 4]
amount2 = 6
print(coinChange(coins2, amount2)) # 输出:[3, 3]
coins3 = [5]
amount3 = 10
print(coinChange(coins3, amount3)) # 输出:[5, 5]
代码解释:
-
动态规划数组
dp用于存储从 0 到amount每个金额所需的最小硬币数。- 初始化时,
dp[0] = 0,表示凑出金额0不需要任何硬币。 - 对于其他金额
i,初始化为float('inf'),表示无法通过当前硬币组合凑出该金额。
- 初始化时,
-
遍历硬币面值:
- 对每个硬币面值,更新
dp数组。对于每个金额i,判断如果使用当前硬币coin是否能得到更少的硬币数,即dp[i] = min(dp[i], dp[i - coin] + 1)。
- 对每个硬币面值,更新
-
回溯:
- 从
dp[amount]开始回溯,找出具体使用的硬币组合。每次回溯时,查找满足dp[current_amount] == dp[current_amount - coin] + 1的硬币,并减去该硬币的面值。
- 从
复杂度分析:
- 时间复杂度:
O(amount * n),其中n是硬币的种类数,amount是目标金额。对于每个硬币,遍历从coin到amount的所有金额,更新dp数组。 - 空间复杂度:
O(amount),dp数组的大小为amount + 1。
结果示例:
对于输入:
coins = [1, 2, 5]和amount = 18,输出为[5, 5, 5, 2, 1]。coins = [1, 3, 4]和amount = 6,输出为[3, 3]。coins = [5]和amount = 10,输出为[5, 5]。
这个方法通过动态规划求得最少硬币数,并且通过回溯找到具体的硬币组合。