Day38 力扣动态规划 :70.爬楼梯 |322. 零钱兑换 |279. 完全平方数
70. 爬楼梯 (进阶)
这道题目 爬楼梯之前我们做过,这次再用完全背包的思路来分析一遍
https://programmercarl.com/0070.%E7%88%AC%E6%A5%BC%E6%A2%AF%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%E7%89%88%E6%9C%AC.html
第一印象
用完全背包的思路重新做一遍爬楼梯。
每次可能爬1或2个,这个就是物品,而且可以数量无限,所以是完全背包。
物品的重量是1和2,价值也是重量。
要爬n阶,所以背包的大小是n。
也就是拿物品装满背包有多少种方法。
接下来要看排列还是组合呢?
比如爬3阶,先爬1再爬2 和 先爬2再爬1是两种,所以是排列。
直接开做!!
看完题解的思路
就是用完全背包的思路去做一遍爬楼梯。
这里每次爬的台阶数量可以变成 1 2 3 …… m,也就是有 m 个物品。
而本题只是 1 2 ,两个物品
实现中的困难
没有苦难
感悟
牛逼啊完全背包!!!
代码
class Solution {
public int climbStairs(int n) {
//爬 i 阶楼梯有 dp[i] 种方法
int[] dp = new int[n + 1];
//初始化
dp[0] = 1;
//先背包再物品,排列
for (int j = 1; j < dp.length; j++) {
for (int i = 1; i <= 2; i++) {
if ( j >= i) {
dp[j] += dp[j - i];
}
}
//打印dp数组
for (int k = 0; k < dp.length; k++) {
System.out.print(dp[k] + " ");
}
System.out.println();
}
return dp[n];
}
}
322. 零钱兑换
如果求组合数就是外层for循环遍历物品,内层for遍历背包。 如果求排列数就是外层for遍历背包,内层for循环遍历物品。
这句话结合本题 大家要好好理解。 视频讲解:https://www.bilibili.com/video/BV14K411R7yv
https://programmercarl.com/0322.%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.html
第一印象
我试试
dp数组
凑成 i 元需要的最少硬币数量是 dp[i]
递推公式
dp[j] = min( dp[j], dp[j - coins[I] ] + 1 )
初始化
画出二维数组体验一下过程
首先,dp[0] = 0是肯定的,因为凑出0元就是0种方式,题目的实例也告诉我们了。
其他元素呢?按照习惯 还是dp[i] = 0. 但这里不行了,因为每次都取最小是,如果初始化都是 0 的话,每次取dp[j] 和dp[j-coins[i]] + 1 小的那个,那么dp数组永远都是 0 了。因为dp的含义是凑成 i 元最少需要dp[i] 个硬币。
所以dp数组要初始化为最大,一开始我选择了MAX_VALUE,但其实不合理,后面再说。
遍历顺序
硬币数量和组合排列没关系,我觉得for循环里外都行,我选择了最习惯的组合 —— 先物品再背包
如果凑不出来返回 -1
怎么看凑不出来呢??拿coins 只有2,amount=3 来举例子。
一开始数组都是MAX,从 j=2开始处理,dp[2] = min(dp[2], dp[0] + 1)
所以dp[2]更新成了1
dp[3] = min ( dp[3], dp[1] + 1 )
dp[1] 和dp[3] 都是MAX,那么dp[1] + 1岂不是突破 MAX 了?这就不合理了。
所以数组初始化应该是MAX - 1. 这样的话,dp[1] + 1= MAX, dp[3] = MAX - 1. 选择更小的,dp[3] 更新为MAX - 1.
既保证了不会溢出整形,也保证了数组最大的是MAX - 1,这样就算遇到下一个dp[3] + 1的时候,也不会溢出。
看完题解的思路
我照亮照亮题解是不是这么回事
确实在遍历顺序那里,两个 for 循环内外都可以,因为和顺序没关系。
我觉得可能是这样,一个是逻辑上没关系,另者,递推公式是取最小而不是累加,顺序就不会影响取最小这个计算过程。
因为1 1 2 和 2 1 1,对于几种方法的dp数组来说排列的话是2种,组合的话是1张
但如果dp数组记录的是最小硬币数量,对于dp数组来说,记录的都是 3.
同样,dp数组如果记录的是最大价值,对于dp数组来说,记录的都是那个价值。
剩下的和我想的一样
他是用下面这样的方式避免了MAX溢出
//只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
if (dp[j - coins[i]] != max) {
//选择硬币数目最小的情况
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
实现中的困难
避免max溢出就行了
感悟
我真是天才
代码
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
//初始化
dp[0] = 0;
for (int i = 1; i < dp.length; i++) {
dp[i] = Integer.MAX_VALUE - 1;
}
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j < dp.length; j++) {
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
if (dp[amount] == Integer.MAX_VALUE - 1) return -1;
return dp[amount];
}
}
279.完全平方数
本题 和 322. 零钱兑换 基本是一样的,大家先自己尝试做一做
视频讲解:https://www.bilibili.com/video/BV12P411T7Br
https://programmercarl.com/0279.%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.html
第一印象
背包大小是 n
完全平方数是每个物品
我得先创建这个物品的数组才行。
但是多大呢?
o ,体力给了,背包最多10000大小,那么完全平方数到10000就行,也就是100个完全平方数
总之,完全背包所以正序遍历,和顺序没关系两个for循环里外都行。
我写写试试。
样例跑过了,但是n比较大的时候就超过时间限制了,比如n=6665.
看完题解的思路
我看看怎么优化
哦,原来不用生成那个物品数组,这样会让for循环每次都做的很大。
诶,我按题解的写法怎么也超时了。
我把打印数组的地方去掉了,就通过了?????
尝试了一下,按我自己的方法,去掉打印数组,也是不超时的。好奇怪啊。
实现中的困难
没啥苦难
感悟
无
不知道为什么如果吧打印dp数组的那个拿出来,就会超时了。
代码
class Solution {
public int numSquares(int n) {
//dp数组
int[] dp = new int[n + 1];
//初始化
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
//递推公式
for (int i = 1; i * i <= n; i++) {
for (int j = i * i; j < dp.length; j++) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
// //打印dp数组
// for (int k = 0; k < dp.length; k++) {
// System.out.print(dp[k] + " ");
// }
// System.out.println();
}
return dp[n];
}
}