线性动态规划:最少硬币找零问题(Coin Change)
字数 1350 2025-12-06 21:37:02

线性动态规划:最少硬币找零问题(Coin Change)

问题描述

给定不同面额的硬币和一个总金额。计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

解题过程

我们通过动态规划来解决,重点是定义dp数组以及状态转移方程。

步骤1:定义状态

我们定义 dp[i] 表示凑出金额 i 所需的最少硬币数量。
最终目标是求 dp[amount]。

步骤2:初始化

dp[0] = 0,因为凑出金额0不需要任何硬币。
对于其他 i(1 到 amount),我们初始化为一个很大的数(比如 amount+1 或 INF),表示暂时无法凑出。
因为硬币最小面额为1,最多需要 amount 枚硬币,所以用 amount+1 作为“无穷大”。

步骤3:状态转移方程

对于每个金额 i(从 1 到 amount),我们尝试每一种硬币 coin:
如果 coin <= i,说明可以用这枚硬币,那么 dp[i] 可能等于 dp[i - coin] + 1。
所以状态转移方程为:
dp[i] = min{ dp[i], dp[i - coin] + 1 },其中 coin 是每个硬币面额,且 coin <= i。

步骤4:计算顺序

外层循环枚举金额 i 从 1 到 amount。
内层循环枚举每个硬币,尝试更新 dp[i]。
这样确保在计算 dp[i] 时,所有更小的金额都已经计算过。

步骤5:最终结果

如果 dp[amount] 仍然为初始的大数,则说明无法凑出,返回 -1;否则返回 dp[amount]。

举例说明

以 coins = [1, 2, 5], amount = 11 为例:
初始化 dp[0]=0, dp[1..11]=12。
i=1: 可用硬币1 → dp[1] = min(dp[1], dp[0]+1)=1。
i=2: 硬币1 → dp[2]=min(12, dp[1]+1)=2;硬币2 → dp[2]=min(2, dp[0]+1)=1。
i=3: 硬币1→dp[3]=min(12, dp[2]+1)=2;硬币2→dp[3]=min(2, dp[1]+1)=2。
以此类推,最终 dp[11] 计算过程:

  • 用5 → dp[11] = dp[6] + 1。而 dp[6] 可由 dp[5]+1 得到,dp[5] 可由 5 直接得到(1枚),所以 dp[6]=2(5+1),则 dp[11]=3。
  • 用2 → dp[11] = dp[9] + 1,dp[9] 最小为 3(5+2+2),所以是4,比3大,不更新。
  • 用1 → dp[11] = dp[10] + 1,dp[10] 最小为 2(5+5),所以是3,相同。
    所以最终结果为3。

复杂度分析

时间复杂度:O(amount * n),n 是硬币种类数。
空间复杂度:O(amount)。

代码实现(Python)

def coinChange(coins, amount):
    dp = [amount + 1] * (amount + 1)
    dp[0] = 0
    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)
    return dp[amount] if dp[amount] <= amount else -1

这样就解决了最少硬币找零问题。

线性动态规划:最少硬币找零问题(Coin Change) 问题描述 给定不同面额的硬币和一个总金额。计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 示例: 输入:coins = [ 1, 2, 5 ], amount = 11 输出:3 解释:11 = 5 + 5 + 1 解题过程 我们通过动态规划来解决,重点是定义dp数组以及状态转移方程。 步骤1:定义状态 我们定义 dp[ i ] 表示凑出金额 i 所需的最少硬币数量。 最终目标是求 dp[ amount ]。 步骤2:初始化 dp[ 0 ] = 0,因为凑出金额0不需要任何硬币。 对于其他 i(1 到 amount),我们初始化为一个很大的数(比如 amount+1 或 INF),表示暂时无法凑出。 因为硬币最小面额为1,最多需要 amount 枚硬币,所以用 amount+1 作为“无穷大”。 步骤3:状态转移方程 对于每个金额 i(从 1 到 amount),我们尝试每一种硬币 coin: 如果 coin <= i,说明可以用这枚硬币,那么 dp[ i] 可能等于 dp[ i - coin ] + 1。 所以状态转移方程为: dp[ i] = min{ dp[ i], dp[ i - coin] + 1 },其中 coin 是每个硬币面额,且 coin <= i。 步骤4:计算顺序 外层循环枚举金额 i 从 1 到 amount。 内层循环枚举每个硬币,尝试更新 dp[ i ]。 这样确保在计算 dp[ i ] 时,所有更小的金额都已经计算过。 步骤5:最终结果 如果 dp[ amount] 仍然为初始的大数,则说明无法凑出,返回 -1;否则返回 dp[ amount ]。 举例说明 以 coins = [ 1, 2, 5 ], amount = 11 为例: 初始化 dp[ 0]=0, dp[ 1..11 ]=12。 i=1: 可用硬币1 → dp[ 1] = min(dp[ 1], dp[ 0 ]+1)=1。 i=2: 硬币1 → dp[ 2]=min(12, dp[ 1]+1)=2;硬币2 → dp[ 2]=min(2, dp[ 0 ]+1)=1。 i=3: 硬币1→dp[ 3]=min(12, dp[ 2]+1)=2;硬币2→dp[ 3]=min(2, dp[ 1 ]+1)=2。 以此类推,最终 dp[ 11 ] 计算过程: 用5 → dp[ 11] = dp[ 6] + 1。而 dp[ 6] 可由 dp[ 5]+1 得到,dp[ 5] 可由 5 直接得到(1枚),所以 dp[ 6]=2(5+1),则 dp[ 11 ]=3。 用2 → dp[ 11] = dp[ 9] + 1,dp[ 9 ] 最小为 3(5+2+2),所以是4,比3大,不更新。 用1 → dp[ 11] = dp[ 10] + 1,dp[ 10 ] 最小为 2(5+5),所以是3,相同。 所以最终结果为3。 复杂度分析 时间复杂度:O(amount * n),n 是硬币种类数。 空间复杂度:O(amount)。 代码实现(Python) 这样就解决了最少硬币找零问题。