01背包问题 二维
一开始以为一维dp数组即可,但是在递推时,发现需要每个已知量都需要对应的used数组,来判断装入了哪些东西。
相比于之前只是草草看了一遍,这次动手做发现很多点真不容易想到。
import java.util.*;
class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int m = sc.nextInt(); // 种类数
int n = sc.nextInt(); // 行李空间
int[][] arr = new int[2][m];
for(int i=0; i<m; i++){ // 空间
arr[0][i] = sc.nextInt();
}
for(int i=0; i<m; i++){ // 价值
arr[1][i] = sc.nextInt();
}
int[][] dp = new int[m+1][n+1]; // [i][k]: 已有的前i种种类,在面对k个行李空间时
// 新加入一种种类,只有放入和不放入两种结果。比较两种结果即可。
// 如何判断能否加入该种类?就要看 此时的空间-该物品的空间,这个空间的最大能装多少东西了 所以dp[i][k]递推需要dp[i][k-空间]
for(int i=1; i<m+1; i++){ // 固定的种类数 i
for(int k=1; k<n+1; k++){ // 不固定的行李空间 k
int odd = k-arr[0][i-1]; // 此时的空间能否装下该物品(单独装下该物品)
if(odd>=0) dp[i][k] = Math.max(dp[i-1][k], dp[i-1][odd] + arr[1][i-1]);
else dp[i][k] = dp[i-1][k];
}
}
System.out.println(dp[m][n]);
}
}
01背包问题 一维
使用滚动数组,压缩空间——为了使用到前面的数,必须反向遍历。
import java.util.*;
class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int m = sc.nextInt(); // 种类数
int n = sc.nextInt(); // 行李空间
int[][] arr = new int[2][m];
for(int i=0; i<m; i++){ // 空间
arr[0][i] = sc.nextInt();
}
for(int i=0; i<m; i++){ // 价值
arr[1][i] = sc.nextInt();
}
int[] dp = new int[n+1]; // 需要先固定种类,再固定行李空间
for(int i=0; i<m; i++){
for(int k=n; k>=0; k--){
if(k>=arr[0][i]) dp[k] = Math.max(dp[k], dp[k-arr[0][i]] + arr[1][i]);
}
}
System.out.println(dp[n]);
}
}
代码随想录版本:
- 剪枝:遍历到该物品的空间大小以内时,不再遍历。
- 不可以先遍历背包容量嵌套遍历物品。因为根据二维dp以及其递推公式,发现背包一定要反向遍历。而只有在最内层才能反向遍历。
当然,也正因此,可以实现剪枝。因为之前的都不会变啊,一定不会装下该物品的。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取 M 和 N
int M = scanner.nextInt(); // 研究材料的数量
int N = scanner.nextInt(); // 行李空间的大小
int[] costs = new int[M]; // 每种材料的空间占用
int[] values = new int[M]; // 每种材料的价值
// 输入每种材料的空间占用
for (int i = 0; i < M; i++) {
costs[i] = scanner.nextInt();
}
// 输入每种材料的价值
for (int j = 0; j < M; j++) {
values[j] = scanner.nextInt();
}
// 创建一个动态规划数组 dp,初始值为 0
int[] dp = new int[N + 1];
// 外层循环遍历每个类型的研究材料
for (int i = 0; i < M; i++) {
// 内层循环从 N 空间逐渐减少到当前研究材料所占空间
for (int j = N; j >= costs[i]; j--) {
// 考虑当前研究材料选择和不选择的情况,选择最大值
dp[j] = Math.max(dp[j], dp[j - costs[i]] + values[i]);
}
}
// 输出 dp[N],即在给定 N 行李空间可以携带的研究材料的最大价值
System.out.println(dp[N]);
scanner.close();
}
}
416. 分割等和子集 (难想)
一眼看不出来要动规,所以想出这种解法,只能对一部分。
class Solution {
public boolean canPartition(int[] nums) {
Arrays.sort(nums);
int a=0, b=0;
for(int cur = nums.length-1; cur>=0; cur--){
if(a<b) a += nums[cur];
else b += nums[cur];
}
return a==b;
}
}
本题的本质是,能否把容量为 sum / 2的背包装满。
既有一个 只能装重量为 sum / 2 的背包,商品为数字,这些数字能不能把 这个背包装满。
那每一件商品是数字的话,对应的重量 和 价值是多少呢?
一个数字只有一个维度,即 重量等于价值。
当数字 可以装满 承载重量为 sum / 2 的背包的背包时,这个背包的价值也是 sum / 2。
class Solution {
public boolean canPartition(int[] nums) {
int sum = Arrays.stream(nums).sum();
if (sum % 2 != 0) {
return false;
}
int target = sum / 2; // 容量
int[] dp = new int[target + 1]; // !!!此处为容量+1
for (int i = 0; i < nums.length; i++) {
for (int k = target; k >= nums[i]; k--) { // 容量小于物品体积时,停止循环
dp[k] = Math.max(dp[k], dp[k - nums[i]] + nums[i]); // 不需要限制条件,因为k-nums[i]一定会>=0,是循环条件
}
}
return dp[target] == target;
}
}