硬币找零问题是一个经典的动态规划问题。问题描述如下:
给定一个面值集合(例如:1 元、2 元、5 元、10 元等不同面值的硬币),以及一个目标金额 n
,要求找出凑成这个目标金额的最少硬币数量。
1. 问题定义
假设有 m
种不同面值的硬币 coins={c1,c2,...,cm}\text{coins} = \{c_1, c_2, ..., c_m\}coins={c1,c2,...,cm},我们需要凑出一个金额 n
。要求的是使用最少的硬币数来凑出这个金额。如果不能凑出目标金额,则返回 -1。
举例:
- 硬币面值:[1,2,5][1, 2, 5][1,2,5]
- 目标金额:
11
最少的硬币组合是 [5,5,1][5, 5, 1][5,5,1],所需硬币数为 3。
2. 动态规划思路
这类问题典型的动态规划解法是通过定义一个状态数组 dp[]
,其中 dp[i]
表示凑出金额 i
所需的最少硬币数量。
状态转移方程:
对于金额 i
,我们可以从任意一个面值的硬币 cjc_jcj 出发(只要 i≥cj),如果选择了面值为 cj 的硬币,则问题转化为子问题,即剩下的金额 i−cj:
dp[i]=min(dp[i−cj]+1)for each cj in coins
初始化:
- 当金额为 0 时,所需硬币数为 0,即
dp[0] = 0
。 - 当金额无法凑出时,
dp[i] = \infty
(假设初始化为一个很大的值,如n+1
)。
求解过程:
通过迭代的方式,从金额 1 到 n
逐步填充 dp[]
数组,每次选择最优解。
3. 动态规划解法代码实现
下面是使用动态规划解决硬币找零问题的 Java 代码:
import java.util.Arrays;
public class CoinChange {
// 动态规划求解硬币找零问题
public static int coinChange(int[] coins, int amount) {
// 创建dp数组,初始化为amount+1(表示无法凑出)
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0; // 凑出0元需要0枚硬币
// 遍历每一个金额
for (int i = 1; i <= amount; i++) {
// 遍历每种硬币面值
for (int coin : coins) {
// 如果硬币面值小于等于当前金额,才计算子问题
if (i >= coin) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
// 如果dp[amount]没有被更新,说明无法凑出amount
return dp[amount] > amount ? -1 : dp[amount];
}
public static void main(String[] args) {
int[] coins = {1, 2, 5}; // 硬币面值
int amount = 11; // 目标金额
int result = coinChange(coins, amount);
System.out.println("Minimum coins required: " + result);
}
}
4. 代码详解
4.1 输入与输出
- 输入:硬币面值数组
coins[]
和目标金额amount
。 - 输出:返回凑出目标金额所需的最少硬币数。如果无法凑出,返回 -1。
4.2 关键步骤
-
初始化:
Arrays.fill(dp, amount + 1);
dp[0] = 0;
-
创建并初始化
dp[]
数组。dp[i]
表示凑出金额i
所需的最少硬币数。初始时假设所有金额都无法凑出,设为amount + 1
。dp[0] = 0
表示凑出 0 元不需要硬币。 -
动态规划填表:
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (i >= coin) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
-
遍历金额
i
,对于每个金额,检查每种硬币面值coin
,如果当前硬币面值不超过金额i
,则可以选择该硬币,更新dp[i]
。 -
结果判断:
return dp[amount] > amount ? -1 : dp[amount];
-
如果
dp[amount]
仍然是初始值amount + 1
,说明无法凑出目标金额,返回 -1;否则返回dp[amount]
。
5. 复杂度分析
- 时间复杂度:
O(n * m)
,其中n
是目标金额amount
,m
是硬币的种类数。动态规划需要遍历每个金额,并且对于每个金额,遍历每种硬币面值。 - 空间复杂度:
O(n)
,只需要一个大小为n + 1
的数组dp[]
。
6. 优化与变种
6.1 空间优化
在该问题中,我们只需要用到 dp[i]
和 dp[i - coin]
,因此可以通过空间压缩,使用一维数组来优化空间复杂度,已经在上述实现中应用。
6.2 找到所有可能的组合
除了找到最少硬币数,有时我们还希望找到所有的可能组合。在这种情况下,可以修改算法以存储每一步的解法路径。
6.3 无限硬币与有限硬币
此问题假设硬币数量是无限的。如果硬币数量是有限的,问题可以转化为一个背包问题,解决方法也会有所不同。
7. 总结
硬币找零问题是动态规划中的经典应用,通过建立状态转移方程,可以有效地解决最优子结构问题。此问题在现实中也有广泛应用,例如找零系统的设计、货币兑换、物品装箱等。