最优硬币组合问题 | 豆包MarsCode AI 刷题

106 阅读4分钟

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

  • 例如:小C有三种硬币,面值分别为 125。他需要凑出总金额为 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)。完全背包问题是一种组合优化的数学问题,其中目标是在不超过背包容量的前提下,从给定的物品中选择物品,使得背包中物品的总价值最大或总重量最小。在这个问题中,我们的目标是找到最少的硬币数量,使得它们的总和等于给定的金额。

以下是代码的求解思路:

  1. 初始化DP数组

    • dp数组用于存储构成每个金额所需的最少硬币数量。dp[i]表示构成金额i所需的最少硬币数量。
    • dp[0]初始化为0,因为构成金额0不需要任何硬币。
    • coins_used数组用于存储构成每个金额的硬币组合。
  2. 填充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的另一种可能的硬币组合。
  3. 检查结果

    • 如果dp[amount]的值是float('inf'),表示无法构成给定的金额,返回-1。
    • 如果可以构成给定的金额,通过回溯coins_used数组找到构成金额amount的硬币组合。
  4. 回溯找到硬币组合

    • 从金额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的硬币组合列表