https://leetcode-cn.com/problems/coin-change-2/submissions/
题目:
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 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
注意:
你可以假设:
0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数
思路
拿到题目想到dp,看到硬币无限个想到完全背包问题(每个硬币的价值为1,成本为coin,总容量为amount),然后进陷入了一个思维,导致最后都没写出来。
正解:设f(i,j)代表拿前i种硬币,可以拼凑成j元的方案数字
。
递推关系如下:
f(i,j) = f(i-1,j)+f(i,j-coins[j]) j >= coins[j]
f(i,j) = f(i-1,j) i < coins[j]
初始化问题:
按照背包的思路,初始化一个二维的dp数组,行数是0~coins.length
,列数是0~amount+1
。
当0元时,不管拿几种硬币,方案数都是1。
其他情况,当前钱数%coins[i]==0
时,方案数是1。
代码
public class Solution_518 {
public static void main(String[] args) {
change(5, new int[]{1, 2, 5});
}
public static int change(int amount, int[] coins) {
int N = amount + 1;
int[] weight = new int[coins.length + 1];
for (int i = 1; i < weight.length; i++) {
weight[i] = coins[i - 1];
}
int[][] dp = new int[weight.length][N];
// 初始化
for (int i = 0; i < weight.length; i++) {
for (int j = 0; j < N; j++) {
if (i == 0 && j != 0) {
dp[i][j] = 0;
} else if (j == 0) {
dp[i][j] = 1;
} else if (j % weight[i] == 0) {
dp[i][j] = 1;
} else {
dp[i][j] = 0;
}
}
}
for (int i = 1; i < weight.length; i++) {
for (int j = 1; j < N; j++) {
if (j >= weight[i]) {
// 拿得起
dp[i][j] = dp[i][j - weight[i]] + dp[i - 1][j];
} else {
// 拿不起
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[coins.length][amount];
}
}
优化方案
思考f(i,j) = f(i-1,j)+f(i,j-coins[j])
,当前网格用到了上一轮次
的数据f(i-1,j)
,以及当前轮次
之前更新过的数据f(i,j-coins[j])
。
如何用一维数组来代表当前的二维数组呢,即dp[i]
代表了dp[i-1][j]
和dp[i-coins[j]]
代替dp[i][j-coins[j]]
,仔细想一下,如果从i=coin
开始遍历到amount+1
,那如果运行到了dp[i]还没更新,dp[i]=dp[i] + dp[i-coin]
中右边的dp[i]
其实是上一轮的数据,dp[i-coin]
其实是当前轮次之前更新过的数据。
其实和背包问题的优化方案一样,新建一个一维dp数组,大小为amount+1
。
代码
public static int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {
for (int i = coin; i < amount + 1; i++) {
dp[i] = dp[i] + dp[i - coin];
}
}
return dp[amount];
}