返回

一枚硬币的叮当,解析LeetCode 518 零钱兑换的最优策略

前端

LeetCode 518 - 零钱兑换:最优解

序言

算法,对于前端工程师而言,既熟悉又陌生。或许我们不像后端工程师那般重视算法能力,但事实上,算法对每一位程序员都至关重要。因为开发的本质就是将实际问题转化为计算机可识别的指令,而算法正是完成这一转化过程的核心。

问题引入

LeetCode 518 零钱兑换问题是一个经典的动态规划问题。它了一个现实场景:我们有一堆面值不同的硬币,需要用这些硬币凑出指定金额的零钱。我们的目标是找到一种使用硬币数量最少的方案,也就是最优解。

动态规划的魅力

动态规划是一种自底向上的算法范式,特别适用于解决具有重叠子问题的问题。在零钱兑换问题中,对于每一个硬币面值和金额,我们都可以通过考察其子问题(更小的面值和金额)的最优解来推导出当前问题的最优解。

具体来说,我们可以定义一个二维数组 dp,其中 dp[i][j] 表示使用面值不大于 i 的硬币凑出金额 j 所需的最小硬币数量。

算法步骤

  1. 初始化:将 dp[0][j] 设为无穷大,表示无法凑出金额 j;将 dp[i][0] 设为 0,表示凑出金额 0 不需要硬币。
  2. 状态转移:对于每一个 ij,有两种选择:
    • 使用面值为 i 的硬币:dp[i][j] = min(dp[i][j], dp[i - 1][j - i] + 1)
    • 不使用面值为 i 的硬币:dp[i][j] = dp[i - 1][j]
  3. 求解最优解:当 j 为目标金额时,dp[i][j] 即为使用面值不大于 i 的硬币凑出目标金额的最优解。

代码实现

def min_coins(coins, amount):
    """
    :param coins: 硬币面值列表
    :param amount: 目标金额
    :return: 最小硬币数量
    """
    n = len(coins)
    dp = [[float('inf')] * (amount + 1) for _ in range(n + 1)]

    for i in range(n + 1):
        dp[i][0] = 0

    for i in range(1, n + 1):
        for j in range(1, amount + 1):
            if coins[i - 1] <= j:
                dp[i][j] = min(dp[i][j], dp[i - 1][j - coins[i - 1]] + 1)
            dp[i][j] = min(dp[i][j], dp[i - 1][j])

    return dp[n][amount]

进阶:优化算法

对于某些特殊情况,我们可以优化算法以提高效率。例如,如果硬币面值是递增的,那么我们只需要考虑当前面值的硬币和前一个面值的硬币,即可推导出最优解。

结语

零钱兑换问题不仅是一个算法练习题,更是一个实际生活中的常见场景。通过理解和掌握动态规划的精髓,我们可以解决各种复杂问题,为现实世界的应用程序增添一丝算法的魅力。