问题描述
小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。
例如:小C有三种硬币,面值分别为 1, 2, 5。他需要凑出总金额为 18。一种最优的方案是使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。
根据问题的描述,我们需要找到一种方法,使用最少数量的硬币来凑出给定的总金额。这是一个典型的动态规划问题,其中涉及到如何选择硬币以最小化使用的数量。
以下是使用动态规划解决这个问题的Python代码:
python复制代码
def coin_change(coins, amount):
# 初始化一个数组,dp[i] 表示凑出金额 i 所需的最少硬币数量
# 初始化为一个较大的数(这里使用 amount + 1,因为最多不会超过 amount 个面值为 1 的硬币)
dp = [amount + 1] * (amount + 1)
# 凑出金额 0 不需要任何硬币
dp[0] = 0
# 遍历所有可能的金额
for i in range(1, amount + 1):
# 遍历所有硬币面值
for coin in coins:
# 如果当前硬币面值小于等于当前金额
if coin <= i:
# 更新凑出当前金额所需的最少硬币数量
# dp[i - coin] + 1 表示使用当前硬币后,剩余金额所需的最少硬币数量加 1
dp[i] = min(dp[i], dp[i - coin] + 1)
# 如果 dp[amount] 仍然是初始化的较大数,说明无法凑出该金额
if dp[amount] == amount + 1:
return []
# 回溯找到使用的硬币组合
coins_used = []
current_amount = amount
while current_amount > 0:
# 找到能凑出当前金额的最小硬币面值(这里假设 coins 数组已排序,如果不是,则需要先排序)
# 由于我们是从大到小更新 dp 数组的(隐式地,通过遍历所有硬币面值),所以这里可以直接从 coins 数组末尾开始找
# 但为了保持代码的通用性,我们不依赖排序,而是遍历 coins 数组找到第一个能凑出 current_amount - coin 的硬币
for coin in coins:
if current_amount - coin >= 0 and dp[current_amount] == dp[current_amount - coin] + 1:
coins_used.append(coin)
current_amount -= coin
break
# 由于我们是倒序添加的硬币,所以不需要反转结果
return coins_used
# 测试样例
print(coin_change([1, 2, 5], 18)) # 输出: [5, 5, 5, 2, 1] 或其他有效组合,但总数为5
print(coin_change([1, 3, 4], 6)) # 输出: [3, 3]
print(coin_change([5], 10)) # 输出: [5, 5]
注意:
- 在上面的代码中,
dp数组用于存储凑出每个金额所需的最少硬币数量。初始时,除了dp[0]为 0 外,其他所有元素都设置为一个较大的数(这里使用amount + 1)。 - 通过两层循环,我们更新
dp数组。外层循环遍历所有可能的金额,内层循环遍历所有硬币面值。如果当前硬币面值小于等于当前金额,则尝试更新dp[i]。 - 在回溯找到硬币组合时,我们遍历
coins数组,找到第一个能凑出current_amount - coin的硬币,并将其添加到coins_used列表中。由于动态规划的性质,我们总是选择能凑出剩余金额所需硬币数量最少的那个硬币(即使存在多个相同面值的硬币,由于我们是从dp数组的值来判断的,所以总是选择“最优”的那个)。 - 代码中的注释提供了对算法步骤的详细解释。
- 请注意,对于相同的输入,可能存在多个有效的硬币组合,但它们的总数(即使用的硬币数量)是相同的。上面的代码可能会输出这些有效组合中的任何一个。