最优硬币组合问题分析

175 阅读4分钟

最优硬币组合问题分析

问题描述
最优硬币组合问题是一个经典的动态规划问题,旨在找到一种用最少硬币数目凑成目标金额的方案。给定一种硬币面值的集合和目标金额,我们需要找出一种硬币组合,使得硬币的总数量最少,并且能够完全拼凑出目标金额。

1. 问题分析

小C有多种不同面值的硬币,每种硬币的数量是无限的。目标是使用最少的硬币数量凑出一个给定的金额。

关键点

  • 硬币的面值和数量无限制。
  • 目标是找到一个硬币组合,使得总金额为N,并且硬币的数量尽可能少。

举例: 假设硬币面值为 [1, 2, 5],目标金额为 18,最优的组合应为 [5, 5, 5, 2, 1],即使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共5个硬币。

2. 动态规划的思路

动态规划(Dynamic Programming, DP)是解决最优硬币组合问题的有效方法。具体步骤如下:

2.1 状态定义

我们定义一个数组 dp,其中 dp[i] 表示组成金额 i 所需的最小硬币数。我们的目标是计算出 dp[amount],即组成目标金额 amount 所需的最小硬币数。

2.2 状态转移

  1. 初始化:dp[0] = 0,因为要组成金额 0 不需要任何硬币。

  2. 对于每个金额 i(从 1 到 amount),我们尝试用不同的硬币面值来更新 dp[i]。对于每个硬币 coin,如果 i - coin >= 0,那么我们可以通过 dp[i - coin] + 1 来更新 dp[i]。具体来说:

    dp[i]=min⁡(dp[i],dp[i−coin]+1)dp[i]=min(dp[i],dp[i−coin]+1)

    这个转移表示,如果我们已经知道如何用最少的硬币组成金额 i - coin,那么只需要再加上一个 coin 面值的硬币,就能组成金额 i

2.3 结果回溯

为了得到最优的硬币组合,我们还需要保存选择的硬币面值。在每次更新 dp[i] 时,我们可以记录下导致最小硬币数的硬币面值。最终,我们可以通过回溯 dp 数组,得到组成目标金额的硬币组合。

3. 实现步骤

3.1 动态规划求最少硬币数

pythonCopy Code
def coinChange(coins, amount):
    # 初始化 dp 数组,dp[i] 表示组成金额 i 的最少硬币数
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0  # 组成 0 的最小硬币数为 0

    # 动态规划填充 dp 数组
    for i in range(1, amount + 1):
        for coin in coins:
            if i - coin >= 0:
                dp[i] = min(dp[i], dp[i - coin] + 1)

    return dp[amount] if dp[amount] != float('inf') else -1

3.2 回溯得到最优硬币组合

pythonCopy Code
def coinChangeCombination(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    last_coin = [-1] * (amount + 1)  # 用来存储选择的硬币面值

    for i in range(1, amount + 1):
        for coin in coins:
            if i - coin >= 0 and dp[i] > dp[i - coin] + 1:
                dp[i] = dp[i - coin] + 1
                last_coin[i] = coin

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

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

    return result

4. 时间复杂度分析

  • 动态规划部分:我们需要填充一个大小为 amount + 1 的数组,每个元素需要遍历所有的硬币面值。假设硬币数量为 m,则时间复杂度为 O(m×amount)O(m×amount)。
  • 回溯部分:回溯过程中,我们最多需要访问 amount 次,因此时间复杂度为 O(amount)O(amount)。

综上所述,整体时间复杂度为 O(m×amount)O(m×amount),空间复杂度为 O(amount)O(amount)。

5. 例子

示例 1

输入:coins = [1, 2, 5], amount = 18

动态规划计算后的 dp 数组结果为:

Copy Code
dp[18] = 5 (表示最少需要 5 个硬币组成 18)

回溯得到的最优组合为:

Copy Code
[5, 5, 5, 2, 1]

示例 2

输入:coins = [1, 3, 4], amount = 6

动态规划计算后的 dp 数组结果为:

Copy Code
dp[6] = 2

回溯得到的最优组合为:

Copy Code
[3, 3]

示例 3

输入:coins = [5], amount = 10

动态规划计算后的 dp 数组结果为:

Copy Code
dp[10] = 2

回溯得到的最优组合为:

Copy Code
[5, 5]

6. 总结

最优硬币组合问题是一类典型的动态规划问题,目标是通过最少的硬币组合凑出指定金额。通过合理的动态规划设计,我们不仅可以得到最少硬币数,还可以通过回溯的方法得到具体的硬币组合。这个问题在实际应用中非常常见,比如在货币兑换、支付系统等场景中,解决类似问题可以帮助设计更加高效的支付策略。