返回

敲定取舍:LeetCode 动态规划解析之零钱兑换 II

后端

引言

动态规划(Dynamic Programming,DP)是一门重要的算法设计思想,它往往可以将一个大问题拆解成一系列较小的子问题,进而解决大问题。LeetCode 作为一道经典的编程练习题,它考察了我们运用动态规划解决问题的思维能力。本文将带领你深入剖析零钱兑换 II 问题,为你揭开动态规划的神秘面纱。

问题概述

LeetCode 动态规划之零钱兑换 II 问题是这样的:

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。问有多少种不同的方式可以组成 amount,其中每种硬币都可以使用任意多次。

例如,给定 coins = [1, 2, 5] 和 amount = 5,有以下几种方式可以组成 5:

  • 5 个 1 元硬币。
  • 3 个 1 元硬币和 1 个 2 元硬币。
  • 1 个 1 元硬币和 2 个 2 元硬币。
  • 1 个 5 元硬币。

因此,答案为 4。

解题思路

零钱兑换 II 问题的解题思路是基于动态规划的思想。具体来说,我们首先将总金额 amount 分解成一系列较小的子问题,然后逐一求解这些子问题。最终,我们将这些子问题的解汇总起来,就得到了总问题的解。

  1. 定义子问题:对于给定的总金额 amount,我们可以定义子问题为「使用硬币 coins 中的某些面额,有多少种不同的方式可以组成金额 amount」。

  2. 递归求解:为了求解子问题,我们可以使用递归的方法。具体来说,我们可以枚举硬币 coins 中的所有面额,并尝试将每个面额的硬币添加到金额 amount 中。如果硬币的面额小于 amount,那么我们可以递归求解子问题「使用硬币 coins 中的某些面额,有多少种不同的方式可以组成金额 amount - 面额」。

  3. 存储中间结果:为了避免重复计算,我们可以将已经求解过的子问题的解存储起来。这样,当我们再次遇到相同的子问题时,我们可以直接使用存储的解,而无需重新计算。

  4. 汇总结果:当我们求解了所有子问题之后,我们将这些子问题的解汇总起来,就得到了总问题的解。

代码实现

def change(coins, amount):
  """
  求解零钱兑换 II 问题。

  Args:
    coins: 硬币面额数组。
    amount: 总金额。

  Returns:
    有多少种不同的方式可以组成 amount。
  """

  # 创建一个字典来存储子问题的解。
  memo = {}

  def dp(remaining_amount):
    """
    递归求解子问题。

    Args:
      remaining_amount: 剩余金额。

    Returns:
      有多少种不同的方式可以组成 remaining_amount。
    """

    # 如果剩余金额为 0,那么只有一种方法可以组成,即不使用任何硬币。
    if remaining_amount == 0:
      return 1

    # 如果剩余金额为负数,那么没有办法组成。
    if remaining_amount < 0:
      return 0

    # 如果已经计算过该子问题的解,则直接返回。
    if remaining_amount in memo:
      return memo[remaining_amount]

    # 枚举硬币面额,并尝试将每个面额的硬币添加到剩余金额中。
    total_ways = 0
    for coin in coins:
      total_ways += dp(remaining_amount - coin)

    # 将该子问题的解存储到字典中。
    memo[remaining_amount] = total_ways

    # 返回该子问题的解。
    return total_ways

  # 返回总问题的解。
  return dp(amount)

总结

本文详细介绍了 LeetCode 动态规划之零钱兑换 II 问题的解题思路和代码实现。通过这篇文章,相信你已经对动态规划有了一个更加深入的了解。希望你能将学到的知识应用到其他编程问题中,并取得成功。