第四十五天| 第九章 动态规划 part07 70. 爬楼梯 (进阶) 322. 零钱兑换 279. 完全平方数
一、70. 爬楼梯 (进阶)
-
题目链接:https://leetcode.cn/problems/climbing-stairs/
-
题目介绍:
-
假设你正在爬楼梯。需要
n
阶你才能到达楼顶。每次你可以爬
1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?示例 1:
输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
-
-
思路:
-
之前采用DP五部曲的基本思路分析过一次
-
但其实本题稍微进阶一点,就变成一个完全背包问题。
-
比如题目改为:如果一次可以爬1阶、2阶、3阶或者…m阶(这里从1开始到m都是连续的正整数),这时你应该怎么求解。
-
这里就用到了完全背包的思路,还是回到本题中:
-
这里的物品相当于有两个,分别是1阶和2阶
-
背包相当于最高阶楼梯n
-
本题就转化为,我们有两个物品,有一个最大容量为n的背包,每个物品有无限个,怎么装满这个背包(装满这个背包的方法有多少种)
-
按照完全背包的DP五部曲分析:
-
(1)确定dp数组的下标及含义:
-
dp[j]:表示的是装满容量为j的背包,共有dp[j]种方法
-
-
(2)确定递推公式:
-
dp[j] += dp[j - i];
-
-
(3)初始化dp数组:
-
dp[0] = 1;
-
-
(4)确定遍历顺序:
-
因为这是一道求排列数的题目,所以必须先遍历背包,再遍历物品
-
for (int j = 1; j <= n; j++) { for (int i = 1; i <= m; i++) { if (j >= i) { dp[j] += dp[j-i]; } } }
-
-
-
-
-
-
代码:
class Solution {
public int climbStairs(int n) {
int m = 2;
int[] dp = new int[n+1];
dp[0] = 1;
for (int j = 1; j <= n; j++) {
for (int i = 1; i <= m; i++) {
if (j >= i) {
dp[j] += dp[j-i];
}
}
}
return dp[n];
}
}
扩展题:爬楼梯
- 题目:如果一个人一步可以爬1或2或3或4或5…或M阶台阶,那么走到楼梯顶(第n阶),有多少种方法?
- 思路:多重背包
- 代码:
二、322. 零钱兑换
-
题目链接:https://leetcode.cn/problems/coin-change/
-
题目介绍:
-
给你一个整数数组
coins
,表示不同面额的硬币;以及一个整数amount
,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回
-1
。你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11 输出:3 解释:11 = 5 + 5 + 1
-
-
思路:
-
本题求解的相当于装满容量为j的背包,所需的最少的物品个数
-
DP五部曲:
-
(1)确定dp数组及下标的含义:
-
dp[j]:表示的是装满容量为j的背包,最少需要dp[j]个物品
-
-
(2)确定递推公式:
-
dp[j] = Math.min(dp[j], dp[j-coins[i]]+1);
-
-
(3)初始化dp数组:
- 因为每次取的是最小值,因此需要把dp数组的每一个值初始化为Integer.MAX_VALUE
-
(4)确定循环顺序:
-
因为这里求的既不是组合数,也不是排列数,因此先遍历哪一个都可以
-
这里还需要注意的是:
- 只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
- 如果dp[j-coins[i]]是初始最大值,+1之后就会变成Integer.MIN_VALUE,那么求min之后就变成Integer.MIN_VALUE。
- 所以要跳过这个。
-
// 先遍历物品,再遍历背包 for (int i = 0; i < coins.length; i++) { for (int j = coins[i]; j <= amount; j++) { if (dp[j - coins[i]] != Integer.MAX_VALUE) { // 只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要 // 否则的话,Integer.MAX_VALUE + 1会变为一个很小的负数 dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1); } } }
-
-
-
-
代码:
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
// 只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
// 否则的话,Integer.MAX_VALUE + 1会变为一个很小的负数
if (dp[j - coins[i]] != Integer.MAX_VALUE) {
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
if (dp[amount] == Integer.MAX_VALUE) return -1;
return dp[amount];
}
}
三、279. 完全平方数
-
题目链接:https://leetcode.cn/problems/perfect-squares/
-
题目介绍:
-
给你一个整数
n
,返回 和为n
的完全平方数的最少数量 。完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,
1
、4
、9
和16
都是完全平方数,而3
和11
不是。示例 1:
输入:n = 12 输出:3 解释:12 = 4 + 4 + 4
-
-
思路:
- 本题的思路和上道题目类似,都是求装满背包的最少物品数量
- 所以看代码就行,我最后会总结一下这两道题的异同,以及需要注意的地方
-
代码:
class Solution {
public int numSquares(int n) {
int max = Integer.MAX_VALUE;
int[] dp = new int[n + 1];
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i * i <= n; i++) {
for (int j = i * i; j <= n; j++) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
return dp[n];
}
}
四、总结:
4.1 总结1:零钱兑换和完全平方数的异同:
- 首先,可以发现这两道题的本质是类似的,都是求装满一个容量为j的背包,所需要最小的物品数量。
- 但是,二者在循环和递推中有一点区别
- 零钱兑换,需要判断当前物品放入背包中,如果背包容量j减去当前物品重量对应的dp数组值为Integer.MAX_VALUE,则跳过此递推公式。
- 而完全平方数就不用判断
- 为什么呢?
- 是因为完全平方数的dp数组,每一个位置的值都会被覆盖掉,虽然初始化的时候均为Integer.MAX_VALUE,但是对于每一个数字,最起码都可以用数字1累加得到,因此就不会有凑不成的情况发生。
- 但是,零钱兑换这道题就会出现凑不成的情况,如果出现这个情况,该位置的值仍为Integer.MAX_VALUE,加1之后就会变成Integer.MIN_VALUE,再求解Math.min就会出错。
- 所以零钱兑换需要判断,完全平方数则不需要
4.2 总结2:目前背包类的题目大致可以分为以下三种及对应的变体
4.2.1 第一种:求解装满背包的最大价值
-
递推公式:
-
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
-
-
初始化:
- 首先考虑下标为0的情况,在考虑其他下标
- 这类问题一般都初始化为0
-
典型题目:
- 416.分割等和子集==(01背包,判断能否装满背包最大重量的一半,能装满返回true,装不满返回false)==
- 1049.最后一块石头的重量II**(01背包,尽可能多的装背包(背包最大重量为总重量的一半))**
4.2.2 第二种:求解装满背包的方法有多少种
-
递推公式:
-
dp[j] += dp[j - weight[i]];
-
-
初始化:
-
一般地:
-
dp[0] = 1;
-
-
典型题目:
- 494.目标和**(01背包)**
- 518.零钱兑换II**(完全背包的组合) —> 先遍历物品再遍历背包**
- 377.组合总和Ⅳ**(完全背包的排列) —> 先遍历背包再遍历物品**
4.2.3 第三种:求解装满背包的最大或最少物品个数
-
递推公式:
-
最大:
-
dp[j] = Math.max(dp[j], dp[j - weight[i]] + 1);
-
-
最小:
-
dp[j] = Math.max(dp[j], dp[j - weight[i]] + 1);
-
-
-
初始化:
-
最大:
- 全部初始化为0
-
最小:
-
int max = Integer.MAX_VALUE;
-
先全部初始化为max,再将dp[0]初始化为0。
-
-
-
典型题目:
- 474.一和零(最大)(01背包,背包是二维的)
- 322.零钱兑换(最小)(完全背包)
- 279.完全平方数(最小)(完全背包)