Day 37 总结
- 自己实现中遇到哪些困难
- 如果求组合数就是外层for循环遍历先物品,内层for遍历背包。
- 如果求排列数就是外层for遍历先背包,内层for循环遍历物品。
- 今日收获,记录一下自己的学习时间
- 20:15 - 21:00
小总结
- 组合总和 Ⅳ: 求组合先物品, 求排列先背包
- 爬楼梯 (进阶): 确定物品是什么, 物品重量是什么. 物品价值是什么, 背包容量是什么
- 求方法数量: dp[i] += dp[i - nums[j]];
- 零钱兑换: 求组合的最小长度, dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
- 完全平方数: 求组合最小长度,确定物品,确定背包
多重背包
给一些物品,但是物品的数量有限.不能无限的去取了.
可以再做完全背包的时候,去计数,
先物品,再容量,再次数(j - k * weight[i])
for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++)
然后也能摊开拆成01背包.
背包问题总结
-
背包递推公式
-
能否装满背包: dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
-
装满背包有几种方法:dp[j] += dp[j - nums[i]]
-
背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
-
装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
-
-
遍历顺序
-
01背包: 二维数组,从小到大,不在意顺序; 一维数组,先物品,再背包(从大到小)
-
完全背包: 求组合,先物品再背包,求排列,先背包再物品
-
377. 组合总和 Ⅳ
视频讲解:动态规划之完全背包,装满背包有几种方法?求排列数?| LeetCode:377.组合总和IV_哔哩哔哩_bilibili
题目链接:377. 组合总和 Ⅳ - 力扣(LeetCode)
题目描述:
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
请注意,顺序不同的序列被视作不同的组合。
顺序对这个题目来说很重要, 如果先遍历数字, 再遍历容量, 不会发现 112 和 211 的区别.
实现思路:
物品 = 数字,物品重量 = 数字大小, 背包容量 = 目标整数。数字无限取 = 完全背包。
dp数组:dp[i][j] 数字 i,填满容量为 j 的背包,有多少种方式。
推导公式: dp[j] = dp[j] + dp[j - num]
初始化:dp[0] = 1. 填满背包0的方式为什么数字都不选.
遍历顺序:依次遍历数字, 容量从小到达遍历背包
代码实现:
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target+1];
dp[0] = 1;
for (int j = 0; j<target+1; j++) {
for (int i=0; i<nums.length; i++) {
if (j - nums[i] < 0) {continue;}
dp[j] += dp[j - nums[i]];
// System.out.println(Arrays.toString(dp));
}
// System.out.println();
}
return dp[target];
}
}
70. 爬楼梯 (进阶)
题目链接:57. 爬楼梯(第八期模拟笔试)
题目描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
实现思路:
物品 = 楼梯, 物品重量 = 楼梯层数, 背包容量 = 爬到第几层. 楼梯层数任选, 完全背包.
dp数组:dp[j] 代表爬第 n 层楼梯有的方法数量
推导公式: dp[j] = dp[j] + dp[j - 本次爬的层数]
初始化:dp[0] = 1. 爬0层只有一种方法,就是不爬.
遍历顺序:先爬1层再爬3层, 和先爬3层再爬1层 是不一样的. 本题求排列,在意顺序. 先背包后物品.
代码实现:
import java.util.Scanner;
import java.util.Arrays;
public class Main{
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
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) continue;
dp[j] += dp[j - i];
}
System.out.println(dp[n]);
}
}
322. 零钱兑换
题目描述:
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
实现思路:
物品 = 硬币, 物品重量 = 硬币价值, 背包容量 = amount总金额. 硬币数量不限,完全背包.
求最少硬币数量,{2,1}和{1,2}相同,不在意顺序,求组合的最小长度, 先物品再背包.
dp数组:dp[j] 使用当前的硬币装满容量j需要的最少数量.
推导公式: dp[j] = Math,min(dp[j], dp[j - 硬币价值]+1)
初始化:求最小,所以先把每个格子初始化为最大整数值.
遍历顺序:不在意顺序,先物品,再背包.
代码实现:
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
for (int i=1; i<amount+1; i++) dp[i] = Integer.MAX_VALUE;
for (int i=0; i<coins.length;i ++) {
for (int j=coins[i]; j<amount+1; j++) {
if (dp[j - coins[i]] == Integer.MAX_VALUE) {continue;} // 防止溢出
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount]; // 检查都没有解
}
}
279.完全平方数
题目链接:279. 完全平方数 - 力扣(LeetCode)
题目描述:
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
实现思路:
物品 = 完全平方数, 物品重量 = 完全平方数的值, 背包容量 = n.
求填满背包的最少元素数量, 求组合长度, 先物品再背包.
dp数组:dp[j] 当前元素填满背包需要的最少数量
推导公式: dp[j] = Math.min(dp[j], dp[j - 完全平方数值] + 1)
初始化:求最少数量, 先初始化为最大值
遍历顺序:先物品,再背包. 物品为 1^2, 2^2, 3^2...., 背包容量 1 .... n
代码实现:
class Solution {
public int numSquares(int n) {
int[] dp = new int[n+1];
for (int i=1; i<n+1; i++) {dp[i] = Integer.MAX_VALUE;}
for (int i=1; i*i <= n; i++) {
for (int j=1; j<= n; j++) {
if (j - i*i < 0) {continue;}
dp[j] = Math.min(dp[j], dp[j - i*i] + 1);
}
}
return dp[n];
}
}
139.单词拆分
题目描述:
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
实现思路:
如果字符串可以由字典里的单词组成,那么必然会先遇到一个符合条件的字符串, 然后再遇到一个符合条件的字符串,然后再遇到一个符合条件的字符串.
使用多个物品填满一个东西, 背包问题, 物品随便用, 完全背包.
物品 = 字典里的单词, 物品重量 = 单词长度且匹配的上, 背包容量 = 字符长度
dp数组:dp[j] , 字符串 0 ~ j 能否匹配的上字典里的一个词. (左开右闭)
推导公式:
如果 dp[i] 为 true, 前置字符串能够匹配的上
查看字典里的哪些词可以接上, 将匹配到的长度设置为 true
初始化:dp[0] = true, 空串什么单词都不放也能匹配成功.
遍历顺序:求排列, apple + pen + apple != apple + apple + pen, 先背包,再物品
肯定不能先物品,因为物品要用在不同的位置,先再每一个位置尝试匹配字典里的所有词.
代码实现:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length()+1];
dp[0] = true;
for (int i=0; i<=s.length(); i++) {
for (String word : wordDict) {
if (dp[i] // 能够匹配到前置字符串
&& i+word.length() <= s.length() // 待匹配字符串
&& s.substring(i, i+word.length()).equals(word)) // 匹配成功
dp[i+word.length()] = true;
}
}
return dp[s.length()];
}
}