问题描述: 小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。
- 例如:小C有三种硬币,面值分别为
1,2,5。他需要凑出总金额为18。一种最优的方案是使用三个5面值的硬币,一个2面值的硬币和一个1面值的硬币,总共五个硬币。 - 测试样例
- 案例一
输入:
coins = [1, 2, 5], amount = 18
输出:[5, 5, 5, 2, 1] - 案例二
输入:
coins = [1, 3, 4], amount = 6
输出:[3, 3]
- 案例一
输入:
求解思路 这段代码的求解思路是基于动态规划(Dynamic Programming, DP)的完全背包问题(Unbounded Knapsack Problem)。完全背包问题是一种组合优化的数学问题,其中目标是在不超过背包容量的前提下,从给定的物品中选择物品,使得背包中物品的总价值最大或总重量最小。在这个问题中,我们的目标是找到最少的硬币数量,使得它们的总和等于给定的金额。
以下是代码的求解思路:
-
初始化DP数组:
dp数组用于存储构成每个金额所需的最少硬币数量。dp[i]表示构成金额i所需的最少硬币数量。dp[0]初始化为0,因为构成金额0不需要任何硬币。coins_used数组用于存储构成每个金额的硬币组合。
-
填充DP数组:
- 遍历从1到
amount的每个金额i。 - 对于每个金额
i,遍历硬币数组array中的每个硬币c。 - 如果使用硬币
c可以减少构成金额i所需的硬币数量(即dp[i - c] + 1 < dp[i]),则更新dp[i]为dp[i - c] + 1,并更新coins_used[i]为coins_used[i - c] + [c],表示构成金额i的一种新组合。 - 如果使用硬币
c不能减少构成金额i所需的硬币数量,但是可以与当前最少硬币数量相等(即dp[i - c] + 1 == dp[i]),则将硬币c添加到coins_used[i]中,表示这是构成金额i的另一种可能的硬币组合。
- 遍历从1到
-
检查结果:
- 如果
dp[amount]的值是float('inf'),表示无法构成给定的金额,返回-1。 - 如果可以构成给定的金额,通过回溯
coins_used数组找到构成金额amount的硬币组合。
- 如果
-
回溯找到硬币组合:
- 从金额
amount开始,反向遍历coins_used数组,找到构成该金额的硬币组合。 - 由于在填充
coins_used时,我们记录了所有可能的硬币组合,因此在回溯时,我们需要检查每个硬币是否是构成当前金额的一部分,并将其添加到结果列表中。 - 最后返回构成金额
amount的硬币组合列表,列表中的硬币按照从大到小的顺序排列。
- 从金额
这种求解思路可以有效地找到构成给定金额所需的最少硬币数量,并且能够回溯找到具体的硬币组合。
def solution(array, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
coins_used = [[] for _ in range(amount + 1)] # 存储构成每个金额的硬币组合
# 动态规划填充表
for i in range(1, amount + 1):
for c in array:
if i >= c and dp[i - c] + 1 < dp[i]: # 确保i-c不会是负数
dp[i] = dp[i - c] + 1
coins_used[i] = coins_used[i - c] + [c]
elif i >= c and dp[i - c] + 1 == dp[i]: # 如果当前硬币数量最少,记录这种组合
coins_used[i].append(c)
# 如果dp[amount]是无穷大,说明无法构成该金额
if dp[amount] == float('inf'):
return -1
else:
# 回溯找到构成amount的硬币组合
result = []
i = amount
while i > 0:
for j, coin in enumerate(array):
if coins_used[i] and coins_used[i][-1] == coin:
result.append(coin)
i -= coin
break
return result
if __name__ == "__main__":
# Add your test cases here
print(solution([1, 2, 5], 18)) # 应该输出构成18的硬币组合列表