返回

零钱兑换二 - 动态规划解法详解与代码实现

后端

算法原理

零钱兑换问题可以表述为:给定一个整数数组 coins 表示N种不同面额的硬币,另给一个整数 amount 表示总金额。计算并返回可以凑成总金额的硬币组合数。

动态规划是一种强大的算法范式,它适用于解决具有最优子结构和重叠子问题性质的问题。零钱兑换问题恰好满足这两个条件:

  • 最优子结构:对于任何子问题,其最优解可以从其子问题的最优解中组合而成。
  • 重叠子问题:对于相同的子问题,我们可能需要重复计算多次。

因此,我们可以利用动态规划的思想来解决零钱兑换问题。

状态定义

对于零钱兑换问题,我们定义状态 dp[i] 表示凑成金额 i 的硬币组合数。

状态转移方程

对于状态 dp[i],我们可以通过以下两种方式来得到:

  1. 如果不使用面额为 coins[j] 的硬币,那么凑成金额 i 的硬币组合数为 dp[i-coins[j]]
  2. 如果使用面额为 coins[j] 的硬币,那么凑成金额 i 的硬币组合数为 dp[i-coins[j]] + dp[coins[j]]

因此,状态转移方程可以表示为:

dp[i] = \sum_{j=0}^{N-1} dp[i-coins[j]] + dp[coins[j]]

其中,N 表示硬币种类的数量。

代码实现

以下是以Python语言实现的零钱兑换问题的动态规划解法:

def change(coins, amount):
  """
  计算凑成总金额的硬币组合数。

  参数:
    coins:表示N种不同面额的硬币。
    amount:表示总金额。

  返回:
    凑成总金额的硬币组合数。
  """

  # 初始化动态规划表
  dp = [0] * (amount + 1)

  # 初始化基准情况
  dp[0] = 1

  # 遍历硬币种类
  for coin in coins:
    # 遍历金额
    for i in range(coin, amount + 1):
      # 计算当前金额的硬币组合数
      dp[i] += dp[i - coin]

  # 返回总金额的硬币组合数
  return dp[amount]

# 测试代码
coins = [1, 2, 5]
amount = 11
result = change(coins, amount)
print(result)

进一步优化

上述解法的时间复杂度为 O(N \times amount),其中 N 表示硬币种类的数量,amount 表示总金额。当硬币种类较多时,该解法可能过于耗时。我们可以通过以下两种方式来进一步优化解法:

  1. 剪枝: 如果在计算过程中发现某个子问题的最优解已经大于等于总金额,那么我们就可以剪枝,不再继续计算该子问题。
  2. 记忆化搜索: 我们可以将已经计算过的子问题的最优解存储起来,这样当再次遇到相同子问题时,我们可以直接从存储中取值,而无需重复计算。

通过以上两种优化方式,我们可以将解法的复杂度降低到 O(N \times amount / coins[0])