518 coin change II (01背包)
https://leetcode.com/problems/coin-change-ii/
Last updated
https://leetcode.com/problems/coin-change-ii/
Last updated
讲得更清楚,得仔细研究下,代表了一大类问题,细分包括物品可不可以重复,以及dp matrix的压缩
class Solution {
public int change(int amount, int[] coins) {
// dp[i]是amount为i的放法数量
int[] dp = new int[amount+1];
// 注意初始化
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
// 一定是正序遍历,这样之前得到的dp[j]值能累加到之后dp[j]上,也就保证了能重复拿无数个
for (int j = coins[i]; j <= amount; j++) {
// 其实是做了dp array的压缩,本应该是二维数组dp[i][j]表示拿了coin i的时候amount为j的放法
// 本应该是dp[i][j] = dp[i-1][j](不拿这个硬币) + dp[i-1][j-coins[i]](拿这个硬币)
//放法 //之前coin在相同amount下积累的放法
dp[j] = dp[j] + dp[j-coins[i]];
//之前coin在去掉一个coins[i]之后积累的放法
//由于j在自增,所有j都减去了coins[i],
//所以之后的j能累积到之前j使用了coins[i]的放法,保证了coin无限
//比如coin为1,那么j为1时放法加上了dp[1-1]=dp[0]=1,
//当j为2时放法中会加上dp[2-1]=dp[1]也就是j为1时候的放法,
//其中就包含了dp[1-1]的放法,依次累加下去
//如果用二维数组则不需要担心顺序问题
}
}
return dp[amount];
}
}
如果用二维做法是这样的
class Solution {
public int change(int amount, int[] coins) {
// dp[i][j]:使用硬币0-i时amount为j的放法
int[][] dp = new int[coins.length][amount+1];
for (int i = 0; i < coins.length; i++) {
dp[i][0] = 1; // 容量为0的背包放法唯一(不放)
}
//只放硬币0时,amount==coins[0]倍数的时候放法为1,其余amount放法为0
for (int j = 0; j <= amount; j++) {
if (j % coins[0] == 0) {
dp[0][j] = 1;
}
}
for (int i = 1; i < coins.length; i++) {
//注意这里从0开始,之所以一维时不用是因为一维天然继承上一次结果,现在如果不够用当前硬币我们要显式继承
for (int j = 0; j <= amount; j++) {
if (j < coins[i]) {
dp[i][j] = dp[i-1][j];
} else {
//这里dp[i][j-coins[i]]天然包括了amount为j-coins[i]的时候使用coins[i]的情况
//所以不需要显式去把用一个两个三个coins[i]的情况加起来
// hint: 如果你希望不用重复使用,那么dp[i][j]=dp[i-1][j]+dp[i-1][j-coins[i]]
dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]];
//由于i和i-1互相不覆盖,所以不管j从大到小遍历还是从小到大都没关系
}
}
}
return dp[coins.length-1][amount];
}
}