LeetCode-518. 零钱兑换 II
题目来源
题目详情
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入: amount = 10, coins = [10]
输出: 1
提示:
1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins
中的所有值 互不相同0 <= amount <= 5000
题解分析
与本题目类型相似的题目还有:322. 零钱兑换
解法一:动态规划-完全背包问题
- 本题与背包问题极其类似,特别是看到【所有】等关键词,以及题目中提到的【假设每一种面额的硬币有无限个】更可以确信使用完全背包解决。
- 我们假设dp[i][j]表示用前i种硬币能拼凑出j金额的组合数,那么什么情况可以推进状态的变化呢?
- 第一种情况是我们只选前i-1种硬币就能拼凑出j金额。
- 第二种情况是现在只能拼凑出j-coins[i-1]金额,但是也使用了i种硬币。这种情况比较难以理解,我们可以这样想,因为每种硬币的个数都是无限的,即使我们之前使用了第i种硬币使硬币金额总数达到了j-coins[i-1],我们还是可以重复使用第i种硬币来使结果金额达到j。
- 综上,我们可以得出动态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j - coins[i]];
- 需要注意的是,以上的状态转移方程只有在j>=coins[i]时才适用,当j<coins[i]时,dp[i][j] = dp[i-1][j],即后一种情况不用考虑。
class Solution {
public int change(int amount, int[] coins) {
int n = coins.length;
// dp[i][j]表示用前i种硬币能拼凑出j金额的组合数。
// dp[i][j] = dp[i-1][j] + dp[i][j - coins[i]];
int[][] dp = new int[n+1][amount+1];
for(int i=0; i <= n; i++){
dp[i][0] = 1;
}
for(int i=1; i<=n; i++){
for(int j=0; j<= amount; j++){
if(j >= coins[i-1]){
dp[i][j] = dp[i-1][j] + dp[i][j - coins[i-1]];
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][amount];
}
}
解法二:动态规划-压缩数组
- 只要碰到动态规划,我们就应该想想能否使用压缩数组进行空间的优化,一般来说都是可以的。
- 与传统的压缩数组类似,本题的完全背包变形问题也可以应用压缩数组优化手段。
- 需要注意的是,在初始化时需要将dp[0]设置为1表示,对于金额0,即使不使用任何硬币也能凑到,所以组合数为1。
class Solution {
public int change(int amount, int[] coins) {
int n = coins.length;
// dp[i][j]表示用前i种硬币能拼凑出j金额的组合数。
// dp[i][j] = dp[i-1][j] + dp[i][j - coins[i]];
int[] dp = new int[amount+1];
dp[0] = 1;
for(int i=1; i<=n; i++){
for(int j=0; j<= amount; j++){
if(j >= coins[i-1]){
dp[j] = dp[j] + dp[j - coins[i-1]];
}
}
}
return dp[amount];
}
}
相似题目
LeetCode-279 完全平方数
LeetCode-322. 零钱兑换
LeetCode-518. 零钱兑换 II
结果展示
Either Excellent or Rusty