@(labuladong的算法小抄)[dp]
leetcode 518. 零钱兑换 II
题目描述
解题思路
错误解法!!
由于是组合数,所以可能会出现重复,比如金额为3,可以用[1, 2]凑出,也可以用[2, 1]凑出,下面这种解法无法避免这种重复。
而完全背包的思路确保了选择硬币的顺序性,将一堆硬币放在背包面前,如1111…2222…555555…(硬币无限),然后从第一个硬币开始选择是否装入背包,先把面额1的硬币全选完,然后再对面额2的硬币选择,因此能够避免重复组合。
/* 错误解法!! */
class Solution {
public int change(int amount, int[] coins) {
if (amount == 0) return 1;
int n = coins.length;
if (n == 0) return 0;
/* 定义:当目标金额为i时,共有dp[i]种硬币组合法 */
int[] dp = new int[amount + 1];
/* base case dp[0] = 1,什么都不做就是一种组合法 */
dp[0] = 1;
for (int i = 1; i <= amount; i++) {
/* 遍历所有选择 */
for (int coin : coins) {
if (i - coin < 0) continue;
dp[i] += dp[i - coin];
}
}
return dp[amount];
}
}
完全背包问题
参考:labuladong的算法小抄P196
题目实质上在问:给定一个可装载重量为amount
的背包和一系列物品coins
,每个物品的重量为coins[i]
,每个物品的数量无限。现在让你装物品,一共有多少种装法,能够把背包恰好装满?
class Solution {
public int change(int amount, int[] coins) {
if (amount == 0) return 1;
int n = coins.length;
if (n == 0) return 0;
/* 定义:对于coins[0...i-1]的面值,当前要凑的金额为j,共有dp[i][j]种硬币组合法 */
int[][] dp = new int[n + 1][amount + 1];
/* base case dp[0][...] = 0 dp[...][0] = 1 */
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= amount; j++) {
if (j - coins[i - 1] < 0) {
/* 容量满了,只能选择不装入背包 */
dp[i][j] = dp[i - 1][j];
} else {
/* 容量没满,选择不装入或装入背包 */
/* 注意和之前的背包问题有所区别 */
/* 之前的背包问题:如果选择装入背包,dp[i][j]应为dp[i-1][j-nums[i-1]],因为每个物品数量仅有1个 */
/* 这里的背包问题:如果选择装入背包,dp[i][j]应为dp[i][j-nums[i-1]],因为每个物品数量无限 */
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
}
}
}
return dp[n][amount];
}
}