这个问题是一个典型的完全背包问题,它是动态规划问题中的一种。在这个问题中,我们的目标是使用最少数量的硬币来凑出给定的总金额。下面我将详细解释如何使用动态规划来解决这个问题,并提供完整的题解。
问题描述
小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。
例如:小C有三种硬币,面值分别为 1, 2, 5。他需要凑出总金额为 18。一种最优的方案是使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。
即给定一个无限供应的硬币集合,每种硬币有一个确定的面值,我们需要找出最少需要多少枚硬币来凑出给定的总金额。硬币的面值集合是 coins,总金额是 amount。
动态规划思路
- 定义状态:
dp[i]表示凑出金额i所需的最少硬币数量。 - 状态转移方程:对于每个金额
i,我们尝试使用每一种面值的硬币。如果当前硬币的面值为coin,那么dp[i]可以由dp[i - coin] + 1转移而来。因此,状态转移方程为:
(原谅笔者不知道怎么打出来,只好插图片了)
- 初始化:
dp[0] = 0,因为凑出金额0不需要任何硬币。 - 计算顺序:从
1到amount依次计算dp[i]。 - 记录路径:为了找到具体的硬币组合,我们需要在计算
dp[i]的同时记录下使用了哪种硬币。可以使用一个数组prev[i]来记录凑出金额i时最后使用的硬币面值。
代码实现
def solution(coins, amount):
# 初始化 dp 数组,所有金额的最小硬币数设为无穷大
dp = [float('inf')] * (amount + 1)
dp[0] = 0 # 凑出金额0不需要硬币
# 遍历每一种硬币
for coin in coins:
# 遍历从 coin 到 amount 的每一个金额
for i in range(coin, amount + 1):
# 更新 dp[i],使用当前硬币后的最小硬币数
dp[i] = min(dp[i], dp[i - coin] + 1)
# 如果 dp[amount] 仍然是无穷大,说明无法凑出金额
if dp[amount] == float('inf'):
return []
# 反向追踪找到硬币组合
result = []
remaining = amount
for coin in sorted(coins, reverse=True):
while remaining >= coin and dp[remaining] == dp[remaining - coin] + 1:
result.append(coin)
remaining -= coin
return result
if __name__ == "__main__":
# 测试样例
print(solution([1, 2, 5], 18) == [5, 5, 5, 2, 1]) # 输出:True
print(solution([1, 3, 4], 6) == [3, 3]) # 输出:True
print(solution([5], 10) == [5, 5]) # 输出:True
代码解释
- 初始化:
dp数组初始化为无穷大,表示初始状态下所有金额的最小硬币数都是无穷大。dp[0]初始化为0,因为凑出金额0不需要任何硬币。 - 状态转移:遍历每一种硬币,对于每个金额
i,如果使用当前硬币后的金额i - coin可以凑出,那么dp[i]可以更新为dp[i - coin] + 1。 - 检查结果:如果
dp[amount]仍然是无穷大,说明无法凑出给定的总金额,返回空列表。 - 反向追踪:从总金额开始,反向追踪找到具体的硬币组合。使用
sorted(coins, reverse=True)确保优先使用面值较大的硬币,这样可以更快地减少剩余金额。
复杂度分析
- 时间复杂度:
O(amount * len(coins)),其中amount是总金额,len(coins)是硬币面值的种类数。 - 空间复杂度:
O(amount),需要存储dp数组。
总结
通过动态规划的方法,我们可以有效地找到最少数量的硬币组合来凑出给定的总金额。关键在于定义好状态,建立状态转移方程,并通过反向追踪找到具体的硬币组合。这种方法在解决类似问题时非常有用,如背包问题、最短路径问题等。
拓展
若是学会了上述题,不妨用这道题练习一下:
问题描述
读取一个带有两个小数位的浮点数,这代表货币价值。 在此之后,将该值分解为多种钞票与硬币的和,每种面值的钞票和硬币使用数量不限,要求使用的钞票和硬币的总数量尽可能少。 钞票的面值是 100,50,20,10,5,2100,50,20,10,5,2。 硬币的面值是 1,0.50,0.25,0.10,0.051,0.50,0.25,0.10,0.05 和 0.010.01。 经过实验证明:在本题中,优先使用面额大的钞票和硬币可以保证所用的钞票和硬币总数量最少。
输入格式 输入一个浮点数 N。
输出格式 输出每种面值的钞票和硬币的需求数量。
数据范围 0≤N≤1000000.00
最后附上题目链接:656. 钞票和硬币 - AcWing题库 www.acwing.com/problem/con…