代码随想录二刷笔记记录
完全背包
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 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.硬币有无限个
2.求凑成总金额的硬币组合数
从条件1可知,本题是一个完全背包问题。
从条件2可知,本题的遍历顺序。
以示例1为例,coin[1,2,5] ,总金额: 5,则有 4 种组合。[1,1,1,1,1] , [1,2,1,1], [2,2,1] , [5]
由此,区别组合与排列, [1,2,2] [2,2,1] 是同一种组合,但以排列的角度,这是两种排列。
组合不强调元素之间的顺序,排列强调元素之间的顺序。
动态规划五部曲
1.确定dp数组及其下标的含义
dp[j]:表示组成总金额 j 的组合数
2.确定递推公式
由494.目标和可知,回顾求装满背包的物品组合,一般递推公式为:
dp[j] += dp[j - num[i]]
则本题下,考虑 coin[i] 的组合总和为 dp[j] ,则 dp[j] 等于所有不考虑 coin[i] 的组合相加。即 dp[j - coin[i]] 的相加。因此公式为:
dp[j] += dp[j - coins[i]]
//所有的 dp[j - coins[i]] 就是不考虑 coins[i]的组合总和
3.初始化
由 LC494 可知,dp[0] 是此类组合问题递推的基础,因此 dp[0] = 1
意义为:组成总金额为 0 的组合只有一个。
4.遍历顺序
以 coin = [5,1] 为例
4.1如果先遍历物品,再遍历背包。可知,每个硬币都只会被放 1 次。即代表出现[1,5] ,但不会出现 [5,1] 的情况。
for(int i = 0;i < coins.length;i++){//先遍历物品
for(int j = coins[i];j <= amount;j++){//后遍历背包容量
dp[j] += dp[j - coins[i]];
}
}
4.2 如果先遍历背包,再遍历物品。因为所有物品的放的次数是无限的,背包里的每个值都会遍历1和5,则会出现 [1,5] , [5,1] 的情况。
for(int j = 0;j <= amount;j++){//先遍历背包容量
for(int i = 0;i < coins.length;i++){//后遍历物品
if(j >= coins[i]){
dp[j] += dp[j - coins[i]];
System.out.print(dp[j] + " ");
}
}
System.out.println();
}
5.推演分析
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
init | 1 | 0 | 0 | 0 | 0 | 0 |
物品0 | 1 | 1 | 1 | 1 | 1 | 1 |
物品1 | 1 | 1 | 2 | 2 | 3 | 3 |
物品2 | 1 | 1 | 2 | 2 | 3 | 4 |
完整代码实现
public int change(int amount, int[] coins) {
//初始化
int[] dp = new int[amount+1];
dp[0] = 1;
//遍历顺序
for (int i = 0;i < coins.length;i++){//先遍历物品
for (int j = coins[i];j <= amount;j++){//遍历背包容量
dp[j] += dp[j - coins[i]];
System.out.print(dp[j] + " ");
}
System.out.println();
}
}
本题考察二维01背包,同样是考察 01 背包,对我们把题目的条件,转化为物品,背包容量,有一定的难度。需要找到其中的关系,将字符串数组中的元素 s 看作物品,将限制条件 m 和 n 转化为背包容量。