返回
一枚硬币的叮当,解析LeetCode 518 零钱兑换的最优策略
前端
2023-12-12 06:23:42
LeetCode 518 - 零钱兑换:最优解
序言
算法,对于前端工程师而言,既熟悉又陌生。或许我们不像后端工程师那般重视算法能力,但事实上,算法对每一位程序员都至关重要。因为开发的本质就是将实际问题转化为计算机可识别的指令,而算法正是完成这一转化过程的核心。
问题引入
LeetCode 518 零钱兑换问题是一个经典的动态规划问题。它了一个现实场景:我们有一堆面值不同的硬币,需要用这些硬币凑出指定金额的零钱。我们的目标是找到一种使用硬币数量最少的方案,也就是最优解。
动态规划的魅力
动态规划是一种自底向上的算法范式,特别适用于解决具有重叠子问题的问题。在零钱兑换问题中,对于每一个硬币面值和金额,我们都可以通过考察其子问题(更小的面值和金额)的最优解来推导出当前问题的最优解。
具体来说,我们可以定义一个二维数组 dp
,其中 dp[i][j]
表示使用面值不大于 i
的硬币凑出金额 j
所需的最小硬币数量。
算法步骤
- 初始化:将
dp[0][j]
设为无穷大,表示无法凑出金额j
;将dp[i][0]
设为 0,表示凑出金额 0 不需要硬币。 - 状态转移:对于每一个
i
和j
,有两种选择:- 使用面值为
i
的硬币:dp[i][j] = min(dp[i][j], dp[i - 1][j - i] + 1)
- 不使用面值为
i
的硬币:dp[i][j] = dp[i - 1][j]
- 使用面值为
- 求解最优解:当
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]
进阶:优化算法
对于某些特殊情况,我们可以优化算法以提高效率。例如,如果硬币面值是递增的,那么我们只需要考虑当前面值的硬币和前一个面值的硬币,即可推导出最优解。
结语
零钱兑换问题不仅是一个算法练习题,更是一个实际生活中的常见场景。通过理解和掌握动态规划的精髓,我们可以解决各种复杂问题,为现实世界的应用程序增添一丝算法的魅力。