零钱兑换II
但本题和纯完全背包不一样,纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数!
回归本题,动规五步曲来分析如下:
-
确定dp数组以及下标的含义
dp[j]:凑成总金额j的货币组合数为dp[j] -
确定递推公式
dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。 -
dp数组如何初始化
首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。那么 dp[0] = 1 有没有含义,其实既可以说 凑成总金额0的货币组合数为1,也可以说 凑成总金额0的货币组合数为0,好像都没有毛病。但题目描述中,也没明确说 amount = 0 的情况,结果应该是多少。这里我认为题目描述还是要说明一下,因为后台测试数据是默认,amount = 0 的情况,组合数为1的。下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。 -
确定遍历顺序
本题中我们是外层for循环遍历物品(钱币),内层for遍历背包(金钱总额),还是外层for遍历背包(金钱总额),内层for循环遍历物(钱币)呢?
所以递推公式:dp[j] += dp[j - coins[i]];
上图的dp[1]表达的是容量为3的情况下去掉当前元素coins[1]也就是2,剩下容量为1,容量为1的组合次数。也就是1,2.
=后的dp[3]是只使用coins[0]的组合数,也就是1,1,1.
class Solution {
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 = 0;j <= amount;j++){
if(j >= coins[i]){
dp[j] += dp[j - coins[i]];
}
}
}
return dp[amount];
}
}
组合总和 Ⅳ
与上一题最大的不同就是该题是求的排列而不是组合
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!
所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
求装满背包有几种方法,递归公式都是一样的,没有什么差别,但关键在于遍历顺序!
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for(int j = 0;j <= target;j++){
for(int i = 0;i < nums.length;i++){
if(j >= nums[i]){
dp[j] += dp[j - nums[i]];
}
}
}
return dp[target];
}
}
爬楼梯(0-1背包)
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n+1];
int[] weight = {1,2};
dp[0] = 1;
for(int i = 0;i <= n;i++){
for(int j = 0;j < weight.length;j++){
if(i >= weight[j]){
dp[i] += dp[i - weight[j]];
}
}
}
return dp[n];
}
}