日常刷题| 豆包MarsCode AI刷题

98 阅读3分钟

问题描述

小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。

例如:小C有三种硬币,面值分别为 125。他需要凑出总金额为 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]

注意:

  1. 在上面的代码中,dp 数组用于存储凑出每个金额所需的最少硬币数量。初始时,除了 dp[0] 为 0 外,其他所有元素都设置为一个较大的数(这里使用 amount + 1)。
  2. 通过两层循环,我们更新 dp 数组。外层循环遍历所有可能的金额,内层循环遍历所有硬币面值。如果当前硬币面值小于等于当前金额,则尝试更新 dp[i]
  3. 在回溯找到硬币组合时,我们遍历 coins 数组,找到第一个能凑出 current_amount - coin 的硬币,并将其添加到 coins_used 列表中。由于动态规划的性质,我们总是选择能凑出剩余金额所需硬币数量最少的那个硬币(即使存在多个相同面值的硬币,由于我们是从 dp 数组的值来判断的,所以总是选择“最优”的那个)。
  4. 代码中的注释提供了对算法步骤的详细解释。
  5. 请注意,对于相同的输入,可能存在多个有效的硬币组合,但它们的总数(即使用的硬币数量)是相同的。上面的代码可能会输出这些有效组合中的任何一个。