问题描述:
给定不同面值的硬币和一个目标金额 amount
,你需要找到组成该金额所需的最少硬币数。如果无法组成该金额,返回 -1
。每种硬币可以使用无限次。
算法步骤:
-
创建动态规划数组
dp
:- 定义一个数组
dp
,其中dp[i]
表示组成金额i
需要的最少硬币数。 - 初始化
dp
数组,长度为amount + 1
,其中每个元素的初始值为amount + 1
(即一个不可能达到的较大值,表示该金额暂时不可达)。 - 特殊情况:
dp[0] = 0
,因为组成金额为 0 时,不需要任何硬币。
- 定义一个数组
-
动态规划填充
dp
数组:- 使用两个嵌套循环,外层循环从 1 遍历到
amount
,内层循环遍历每个可用的硬币面值。 - 对于每一个金额
i
和每一个硬币面值coin
,如果当前硬币面值小于或等于i
,则更新dp[i]
的值为:dp[i] = Math.min(dp[i], dp[i - coin] + 1);
dp[i - coin] + 1
的意思是:如果已经知道用最少的硬币组成金额i - coin
,那么再加上一个当前面值的硬币,即可以组成金额i
,因此可以用dp[i - coin] + 1
这个值来更新dp[i]
。
- 每次取
dp[i]
的最小值,这样可以确保得到的是最少的硬币数。
- 使用两个嵌套循环,外层循环从 1 遍历到
-
返回结果:
- 最后检查
dp[amount]
的值,如果它仍然是大于amount
的初始值,说明无法组成该金额,返回-1
;否则返回dp[amount]
,即为最少硬币数。
- 最后检查
复杂度分析:
- 时间复杂度:
O(S * n)
,其中S
是目标金额amount
,n
是硬币面值的数量。外层循环遍历每个金额,内层循环遍历每个硬币。 - 空间复杂度:
O(S)
,因为我们使用了一个大小为S + 1
的数组dp
来存储中间结果。
示例分析:
-
示例 1:
- 输入:
coins = [1, 2, 5]
,amount = 11
- 输出:3
- 解释:可以用 5 + 5 + 1,总共使用 3 枚硬币。
- 输入:
-
示例 2:
- 输入:
coins = [2]
,amount = 3
- 输出:-1
- 解释:无法用面值 2 的硬币凑出金额 3。
- 输入:
-
示例 3:
- 输入:
coins = [1]
,amount = 0
- 输出:0
- 解释:不需要任何硬币。
- 输入:
通过动态规划,该算法高效地解决了每个金额对应的最少硬币数,避免了暴力递归带来的重复计算问题。
java 代码
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1]; //dp[i] 表示凑成数值为i所需的最少硬币数量
Arrays.fill(dp, amount + 1); //不可能达到 amount + 1,因为硬币最小面额是1
dp[0] = 0;
//更新dp数组
for(int i = 1; i <= amount; ++i) {
for(int coin : coins) {
if(coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] < (amount + 1) ? dp[amount] : -1;
}
}