做题笔记:最少硬币问题| 豆包MarsCode AI 刷题

127 阅读3分钟

做题笔记:最少硬币问题


题目分析

  1. 问题描述:

    • 给定一个正整数总金额 NN 和多种面值的硬币,硬币的数量无限。
    • 要求使用最少数量的硬币组合来凑出总金额 NN。
    • 如果无法凑出总金额,则返回空列表。
  2. 例子:

    • 硬币面值:1,2,51, 2, 5
    • 目标金额:18
    • 最优解:5,5,5,2,15, 5, 5, 2, 1,即总共使用 5 枚硬币
  3. 问题性质:

    • 最优化问题:在所有可能的组合中找到硬币数最少的一种。
    • 无限背包问题:每种硬币可以使用无限次。
    • 动态规划特性:问题可以分解为子问题,通过记录中间结果避免重复计算。

解题思路

  1. 状态定义:

    • 设 dp[i]dp[i] 表示凑出金额 ii 所需的最少硬币数量。
    • 辅助数组 coin_used 记录凑出金额 ii 时使用的最后一枚硬币,便于回溯组合方案。
  2. 转移方程:

    • 假设当前硬币面值为 cc,金额为 xx,转移方程为: dp[x]=min⁡(dp[x],dp[x−c]+1)dp[x] = \min(dp[x], dp[x - c] + 1)

      • 如果使用当前硬币 cc,从金额 x−cx - c 转移到 xx。
      • dp[x−c]+1dp[x - c] + 1 表示之前的最优解加上当前硬币的数量。
  3. 边界条件:

    • dp[0]=0dp[0] = 0:金额为 0 时,不需要硬币。
    • dp[i]=∞dp[i] = \infty(初始值):表示金额 ii 暂时无法凑出。
  4. 回溯路径:

    • 根据 coin_used 数组,从目标金额开始回溯,依次记录使用的硬币,直到金额为 0。
  5. 复杂度分析:

    • 时间复杂度:O(amount×len(coins))O(\text{amount} \times \text{len(coins)}),因为外层遍历硬币,内层遍历金额。
    • 空间复杂度:O(amount)O(\text{amount}),需要额外的 dpdp 和 coin_used 数组。

代码解析

def solution(coins, amount):
    # 初始化 dp 数组和 coin_used 数组
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    coin_used = [-1] * (amount + 1)

    # 动态规划更新 dp 数组
    for coin in coins:
        for x in range(coin, amount + 1):
            if dp[x - coin] + 1 < dp[x]:
                dp[x] = dp[x - coin] + 1
                coin_used[x] = coin

    # 如果无法凑出目标金额,返回空列表
    if dp[amount] == float('inf'):
        return []

    # 回溯硬币组合
    result = []
    while amount > 0:
        result.append(coin_used[amount])
        amount -= coin_used[amount]

    return result

核心部分分析:

  1. 初始化:

    • dpdp 数组初始化为无穷大,表示初始状态下无法凑出任何金额。
    • dp[0]=0dp[0] = 0:凑出金额 0 不需要硬币。
  2. 动态规划递推:

    • 遍历每种硬币,对应金额从 coincoin 到 amountamount 更新 dpdp 数组。
    • 每次更新时,记录当前选择的硬币到 coin_used 数组。
  3. 结果判定:

    • 如果 dp[amount]dp[amount] 仍为无穷大,表示目标金额无法凑出,返回空列表。
  4. 回溯硬币组合:

    • coin_used 数组中逐步找出使用的硬币,直到金额为 0。

总结

  1. 优点:

    • 动态规划方法避免了重复计算,效率高。
    • 回溯路径可以清楚地还原最优硬币组合。
  2. 适用范围:

    • 此方法适用于任意硬币面值组合,且硬币数量无限的问题。
  3. 注意事项:

    • 如果硬币面值不能凑出目标金额,需要特别处理返回空列表。
    • 硬币面值可能不是有序的,代码逻辑中无需排序。
  4. 扩展问题:

    • 如果硬币数量有限,该问题可以用 完全背包 方法扩展解决。