题目
46. 携带研究材料
题解:
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 1. 读取输入
if (!scanner.hasNextInt()) return;
int M = scanner.nextInt(); // 物品的数量 (研究材料的种类)
int N = scanner.nextInt(); // 背包的最大容量 (行李空间)
int[] weight = new int[M];
int[] value = new int[M];
// 读取每个物品的重量 (占用空间)
for (int i = 0; i < M; i++) {
weight[i] = scanner.nextInt();
}
// 读取每个物品的价值
for (int i = 0; i < M; i++) {
value[i] = scanner.nextInt();
}
// 2. 定义二维 dp 数组
// dp[i][j] 表示从 [0~i] 的物品中任意取,放进容量为 j 的背包,所能产生的最大价值
int[][] dp = new int[M][N + 1];
// 3. dp 数组初始化
// 3.1 第一列 (j=0):背包容量为 0,最大价值必然为 0。(Java 默认数组为 0,无需额外写代码)
// 3.2 第一行 (i=0):只考虑第 0 件物品时。
// 只要背包容量 j 大于等于第 0 件物品的重量,就把它装进去,价值更新为 value[0]
for (int j = weight[0]; j <= N; j++) {
dp[0][j] = value[0];
}
// 4. 双重循环遍历,推导状态
// 外层循环:遍历物品(因为 i=0 已经初始化过了,所以从 i=1 开始)
for (int i = 1; i < M; i++) {
// 内层循环:遍历背包容量(二维数组,容量正序遍历即可)
for (int j = 0; j <= N; j++) {
if (j < weight[i]) {
// 情况一:当前背包容量装不下第 i 件物品,只能不装
dp[i][j] = dp[i - 1][j];
} else {
// 情况二:装得下。在"不装"和"装"之间取最大值
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
// 5. 打印结果
// 右下角的元素:考虑了所有 M 件物品,且拥有最大容量 N 时的最大价值
System.out.println(dp[M - 1][N]);
scanner.close();
}
}
滚动数组优化:
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 1. 读取输入
if (!scanner.hasNextInt()) return;
int M = scanner.nextInt(); // 物品的数量 (研究材料)
int N = scanner.nextInt(); // 背包的最大容量 (行李空间)
int[] weight = new int[M];
int[] value = new int[M];
for (int i = 0; i < M; i++) {
weight[i] = scanner.nextInt();
}
for (int i = 0; i < M; i++) {
value[i] = scanner.nextInt();
}
// 2. 定义一维 dp 数组
// dp[j] 表示:容量为 j 的背包,所能装下的物品最大价值
int[] dp = new int[N + 1];
// 3. dp 数组初始化
// Java 中 int 数组默认全为 0。
// 背包容量为 0 时价值为 0,符合逻辑,无需额外初始化代码。
// 4. 双重循环遍历,推导状态
// 外层循环:遍历每一个物品 i
for (int i = 0; i < M; i++) {
// 内层循环:遍历背包容量 j
// 【极其重要】:一维 01 背包,内层循环必须【倒序遍历】!
// 并且遍历的下限是当前物品的重量 weight[i],因为容量小于它时根本装不下,dp[j] 保持原值即可
for (int j = N; j >= weight[i]; j--) {
// 状态转移方程:
// dp[j] (新值) = max( dp[j] (其实是上一层的旧值,代表不拿当前物品),
// dp[j - weight[i]] (上一层的旧值,腾出空间) + value[i] (拿当前物品) )
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
// 5. 打印结果
System.out.println(dp[N]);
scanner.close();
}
}
416. 分割等和子集
题解:
java
class Solution {
public boolean canPartition(int[] nums) {
int n = nums.length;
// 1. 计算总和,判断是否能平分
int sum = 0;
for (int num : nums) {
sum += num;
}
// 如果总和是奇数,绝对不可能平分为两个整数子集
if (sum % 2 != 0) {
return false;
}
// 背包的容量就是总和的一半
int target = sum / 2;
// 2. 定义二维 dp 数组
// dp[i][j] 表示从 [0, i] 个元素中挑选,能否刚好凑成和为 j
boolean[][] dp = new boolean[n][target + 1];
// 3. 初始化 dp 数组
// 3.1 第一列 (j=0):目标和为 0,无论面对什么范围的数字,只要"一个都不选"就能凑出 0,所以都是 true
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
// 3.2 第一行 (i=0):只考虑第 0 个数字 nums[0]
// 只有当目标和 j 恰好等于 nums[0] 时,才能凑成(前提是 nums[0] 不能超过 target)
if (nums[0] <= target) {
dp[0][nums[0]] = true;
}
// 4. 双重循环推导状态
for (int i = 1; i < n; i++) {
for (int j = 1; j <= target; j++) {
if (j < nums[i]) {
// 背包容量不够,装不下当前数字,只能继承不选当前数字的结果
dp[i][j] = dp[i - 1][j];
} else {
// 背包容量足够,可以选也可以不选,只要有一种情况能凑出 j 即可
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
}
// 5. 返回结果:看所有数字挑完后,能否刚好凑出 target
return dp[n - 1][target];
}
}
滚动数组优化:
java
class Solution {
public boolean canPartition(int[] nums) {
// 1. 基础判断与求和
int sum = 0;
for (int num : nums) {
sum += num;
}
// 如果总和是奇数,绝对不可能平分为两个整数子集
if (sum % 2 != 0) {
return false;
}
// 我们的目标是凑出总和的一半
int target = sum / 2;
// 2. 定义一维 dp 数组
// dp[j] 表示能否凑出总和为 j 的子集
boolean[] dp = new boolean[target + 1];
// 3. 初始化
// 如果目标和是 0,一个数字都不选就能达到,所以为 true
// 其他的 Java 默认初始化为 false
dp[0] = true;
// 4. 开始 01 背包的一维推导
// 外层循环:遍历每一个数字(物品)
for (int i = 0; i < nums.length; i++) {
// 内层循环:遍历目标和(背包容量)
// 【核心铁律】:必须从大到小倒序遍历!防止当前数字被重复使用
// 遍历下限是 nums[i],因为如果 j < nums[i],根本装不下,dp[j] 保持原样即可
for (int j = target; j >= nums[i]; j--) {
// 状态转移方程:
// 能否凑出 j = (不选当前数字就能凑出 j) 或者 (选了当前数字,且之前能凑出 j - nums[i])
dp[j] = dp[j] || dp[j - nums[i]];
}
// 【一个小优化】:如果还没遍历完所有物品,就已经能凑出 target 了,提前结束
if (dp[target]) {
return true;
}
}
// 5. 最终返回能否凑出 target
return dp[target];
}
}