322. 零钱兑换
题目来源
题目分析
给定一个硬币面额数组
coins
和一个总金额amount
,我们需要计算凑成该总金额所需的最少硬币个数。如果无法凑成该金额,则返回-1
。
题目难度
- 难度:中等
题目标签
- 标签:数组、动态规划
题目限制
1 <= coins.length <= 12
1 <= coins[i] <= 5000
解题思路
这道题可以使用动态规划来解决,是一个典型的完全背包问题:
-
问题定义:设
dp[i]
为凑成金额i
所需的最少硬币个数,目标是求出dp[amount]
。 -
状态转移:对于每个硬币面额
coin
,如果我们选择使用该硬币,则dp[i] = dp[i - coin] + 1
。我们需要在所有可能的选择中取最小值,即dp[i] = min(dp[i], dp[i - coin] + 1)
。 -
初始化:
- 初始化
dp[0] = 0
,表示凑成金额0
所需的硬币个数为0
。 - 初始化
dp[i] = amount + 1
表示初始状态下无法凑成金额i
。
- 初始化
-
边界条件:
- 如果
dp[amount]
仍然等于amount + 1
,说明无法凑成该金额,返回-1
。 - 否则,返回
dp[amount]
。
- 如果
核心算法步骤
-
初始化:
- 定义
dp
数组,长度为amount + 1
,初始化为amount + 1
,表示无法凑成该金额。
- 定义
-
状态转移:
- 对于每个
coin
,遍历从coin
到amount
的所有金额,更新dp
数组。
- 对于每个
-
输出结果:
- 返回
dp[amount]
,如果dp[amount]
等于amount + 1
,则返回-1
,否则返回dp[amount]
。
- 返回
代码实现
以下是求解零钱兑换问题的 Java 代码:
/**
* 322. 零钱兑换
* @param coins 硬币面额数组
* @param amount 总金额
* @return 凑成总金额所需的最少的硬币个数
*/
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
for (int i = 1; i <= amount; i++) {
dp[i] = amount + 1;
}
for (int coin : coins) {
for (int j = coin; j <= amount; j++) {
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
}
}
return dp[amount] == amount + 1 ? -1 : dp[amount];
}
代码解读
coinChange
方法:- 初始化
dp
数组,dp[i]
表示凑成金额i
所需的最少硬币个数,初始状态下设为amount + 1
表示不可达。 - 外层循环遍历每个
coin
,内层循环更新dp
数组,尝试通过当前coin
凑成不同的金额。
- 初始化
性能分析
- 时间复杂度:
O(n * amount)
,其中n
是coins
数组的长度,amount
是目标金额。 - 空间复杂度:
O(amount)
,因为我们只需要一个长度为amount + 1
的数组dp
。
测试用例
你可以使用以下测试用例来验证代码的正确性:
int[] coins1 = {1, 2, 5};
int amount1 = 11;
int result1 = coinChange(coins1, amount1);
System.out.println(result1);
// 输出: 3 (11 = 5 + 5 + 1)
int[] coins2 = {2};
int amount2 = 3;
int result2 = coinChange(coins2, amount2);
System.out.println(result2);
// 输出: -1 (无法凑成3)
int[] coins3 = {1};
int amount3 = 0;
int result3 = coinChange(coins3, amount3);
System.out.println(result3);
// 输出: 0 (无需任何硬币即可凑成0)
扩展讨论
其他实现
- BFS:
- 也可以使用广度优先搜索 (BFS) 来解决零钱兑换问题,从
amount
开始不断减去coins
中的值,直到凑成0
或无解。
- 也可以使用广度优先搜索 (BFS) 来解决零钱兑换问题,从
总结
通过动态规划的思想,我们能够高效地解决零钱兑换问题。通过合理定义 dp
数组的含义,状态转移方程,以及对初始状态的处理,可以解决类似的完全背包问题。