今天的重点还是理解排列和组合对于dp的不同操作, 和遍历顺序的区别, 同时添加了关于min的操作
爬楼梯(进阶)
想一想, 如果一步可以一个台阶, 两个台阶….m个台阶, 问有多少种方法可以爬到楼顶
可以发现, 爬台阶的次数可以无限次用, 所以其实也是个完全背包问题
- dp[i]数组: 爬到有i个台阶的楼顶, 有dp[i]种方法
- 递推公式: dp[i] = dp[i - j]
- 初始化, 仍然dp[0]为1
- 遍历顺序: 这里1, 2和2, 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];
}
}
零钱兑换
这里问的是最小的硬币个数, 那么很妙的一点其实就是在递推公式里加个min的条件就行了
- dp[j]:凑足总额为j所需钱币的最少个数为dp[j]
- 递推公式: dp[j] = min(dp[j], dp[j - coins[i]] + 1), 意思是加上一个钱币
- 初始化dp[0] = 0. 凑满0元的硬币个数是0, 同时要初始化一个max用来比较最小
- 遍历顺序: 这里是问钱币最小数, 所以是组合或排列不重要, 都可以
(细节: 需要给dp数组进行赋值;递推公式需满足条件: 如果是初始值就跳过)
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
//初始化最大值
int max = Integer.MAX_VALUE;
//为什么不能用增强for, 因为这就变成给取出来的数赋值, 而不是给数组赋
for(int i = 0; i < dp.length; i++){
dp[i] = max;
}
//初始化dp数组
dp[0] = 0;
//开始遍历
for(int i = 0; i < coins.length; i++){
for(int j = coins[i]; j <= amount; j++){
//判断一下不是初始值
if(dp[j - coins[i]] != max){
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
return dp[amount] == max? -1 : dp[amount];
}
}
完全平方数
提议相当于: 完全平方数就是物品(可以无限使用), 凑个正整数n就是背包, 为凑满这个背包最少有多少个物品, 听起来和上一个零钱兑换一模一样
- dp[j]: 和为j的完全平方数的最少数量为dp[j]
- 递推公式: 可以由dp[j - ii]推出, 可以思考一下如何从dp[j - ii]推导dp[j]
- 初始化: dp[0]表示和为0的完全平方数的最小数量所以为0; 同时因为使用min每次取最小值, 一开始要定义MAX_VALUE才可以保证其不被初始值替代
- 遍历: 这里从不是从0开始, 因为其平分还是0, 遍历的条件也是i*i(理解j是和, i是component)
class Solution {
public int numSquares(int n) {
int max = Integer.MAX_VALUE;
int[] dp = new int[n + 1];
//这里也可以直接用<=n替代
for(int i = 0; i < dp.length; i++){
dp[i] = max;
}
//初始化:
dp[0] = 0;
//这里遍历从i开始就好, 因为0的平分还是0
for(int i = 1; i*i <= n; i++){
for(int j = i*i; j <= n; j++){
//注意这里是判断dp数组
if(dp[j - i*i] != max){
//记得加一
dp[j] = Math.min(dp[j], dp[j - i*i] + 1);
}
}
}
//这里直接return dp[n]就好
return dp[n];
}
}