携带研究材料(第七期模拟笔试)
题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的重量,并且具有不同的价值。
小明的行李箱所能承担的总重量是有限的,问小明应该如何抉择,才能携带最大价值的研究材料。每种研究材料可以选择无数次,并且可以重复选择。
解题思路
本题是一个完全背包问题,与0-1 背包问题不同的是,每种物品可以选择无数次。我们可以使用动态规划来求解。
1. 定义状态
设 dp[i][j] 表示前 i 种物品,背包容量为 j 时能获得的最大价值。
2. 状态转移方程
-
如果当前物品
i的重量weight[i]大于 当前背包容量j,则不能选择该物品:
[
dp[i][j] = dp[i-1][j]
] -
如果可以选择该物品,我们有两种选择:
- 不选该物品,价值等于
dp[i-1][j] - 选择该物品,价值等于
dp[i][j - weight[i]] + value[i](注意这里仍然是dp[i][...],因为可以重复选择)
取两者的最大值:
[
dp[i][j] = \max(dp[i-1][j], dp[i][j - weight[i]] + value[i])
] - 不选该物品,价值等于
3. 边界初始化
- 只有第一种物品时,
dp[0][j]只依赖于dp[0][j - weight[0]],按照weight[0]递推。
4. 时间复杂度
- 采用二维
dp表存储状态,时间复杂度为 O(n \times bagWeight),空间复杂度也是 O(n \times bagWeight)。 - 可以优化为一维数组,从
j=0开始正向更新dp[j],这样空间复杂度优化为 O(bagWeight)。
Java 代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(); // 物品种类
int bagWeight = scanner.nextInt(); // 背包容量
int[] weight = new int[n];
int[] value = new int[n];
for (int i = 0; i < n; i++) {
weight[i] = scanner.nextInt();
value[i] = scanner.nextInt();
}
int[][] dp = new int[n][bagWeight + 1];
// 初始化第一种物品
for (int j = weight[0]; j <= bagWeight; j++) {
dp[0][j] = dp[0][j - weight[0]] + value[0];
}
// 动态规划
for (int i = 1; i < n; i++) {
for (int j = 0; j <= bagWeight; j++) {
if (j < weight[i]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
}
}
}
System.out.println(dp[n - 1][bagWeight]);
scanner.close();
}
}
代码优化:一维 dp 数组(滚动数组优化)
我们可以只用一维 dp 数组存储状态,从 j=0 开始正向遍历,这样保证物品可重复选择:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int bagWeight = scanner.nextInt();
int[] weight = new int[n];
int[] value = new int[n];
for (int i = 0; i < n; i++) {
weight[i] = scanner.nextInt();
value[i] = scanner.nextInt();
}
int[] dp = new int[bagWeight + 1];
// 遍历物品
for (int i = 0; i < n; i++) {
// 这里要正序遍历,确保物品可以被多次选择
for (int j = weight[i]; j <= bagWeight; j++) {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
System.out.println(dp[bagWeight]);
scanner.close();
}
}
518. 零钱兑换 II
题目描述
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
解题思路
本题是一个完全背包问题,与 0-1 背包问题不同的是,每种物品(硬币)可以选择无限次。
1. 定义状态
设 dp[j] 表示凑成金额 j 的硬币组合数。
2. 状态转移方程
- 对于每个硬币
coins[i],遍历所有金额j(从coins[i]到amount)。 - 状态转移方程:
[
dp[j] = dp[j] + dp[j - coins[i]]
]dp[j]:不使用当前硬币coins[i]时,凑成j的组合数。dp[j - coins[i]]:使用当前硬币coins[i]后,凑成j的组合数。
3. 初始化
dp[0] = 1,表示凑成金额0的方案数为1(不使用任何硬币)。- 其余
dp[j]初始化为0。
4. 遍历顺序
- 外层遍历硬币种类,确保每种硬币可以被多次选择。
- 内层遍历金额,从
coins[i]开始递增,保证组合数不会重复计算。
5. 时间复杂度
- O(n \times amount),其中
n是硬币种类数,amount是目标金额。 - 空间复杂度 O(amount),只使用一维
dp数组。
Java 代码实现
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount+1];
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
dp[j] = dp[j] + dp[j - coins[i]];
}
}
return dp[amount];
}
}
组合总和 IV
题目描述
给定一个由正整数组成且不存在重复数字的数组 nums,找出和为给定目标正整数 target 的组合的个数。
解题思路
本题属于完全背包问题,不同点在于:
- 组合问题:顺序不同的组合视为不同的方案。
- 完全背包:每个数可以被多次使用。
1. 定义状态
设 dp[j] 表示凑成目标和 j 的组合数。
2. 状态转移方程
对于每个 j,遍历 nums 中的所有元素 nums[i],如果 j >= nums[i],则:
[
dp[j] += dp[j - nums[i]]
]
dp[j - nums[i]]代表去掉nums[i]之前的方案数。dp[j]通过遍历nums[i]进行累加。
3. 初始化
dp[0] = 1,表示凑成 0 的唯一方式是选取空集合。- 其余
dp[j]初始化为0。
4. 遍历顺序
- 先遍历
j(目标和),再遍历nums[i]。 - 保证不同顺序的组合能被计算到。
- 由于顺序不同的组合视为不同方案,因此需要先遍历目标和,再遍历数组。
5. 时间复杂度
- O(target × n),其中
n是nums的大小。 - 空间复杂度 O(target),使用一维
dp数组。
Java 代码实现
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for (int j = 0; j <= target; j++) {
for (int i = 0; i < nums.length; i++) {
if (j >= nums[i]) {
dp[j] += dp[j - nums[i]];
}
}
}
return dp[target];
}
}
爬楼梯问题
题目描述
假设你正在爬楼梯。需要 n 阶才能到达楼顶。
每次你可以爬 1 或 2 个台阶。请问有多少种不同的方法可以爬到楼顶?
解题思路
本题是一个动态规划(DP)问题,类似于斐波那契数列。
1. 定义状态
设 dp[j] 表示爬到第 j 阶的方法数。
2. 状态转移方程
- 每次可以爬
1或2个台阶,因此:
[
dp[j] = dp[j-1] + dp[j-2]
] dp[j-1]代表最后一步爬1阶的情况。dp[j-2]代表最后一步爬2阶的情况。
3. 初始化
dp[0] = 1(空楼梯只有一种方式,不动)。dp[1] = 1(爬 1 阶只能有 1 种方式)。dp[2] = 2(可以走1+1或2)。
4. 遍历顺序
- 由于
dp[j]依赖于dp[j-1]和dp[j-2],需从小到大遍历j。
5. 时间复杂度
- O(n),因为
dp数组需要遍历n次。 - 空间复杂度 O(n),可以进一步优化到
O(1)使用滚动变量。
Java 代码实现
class Solution {
public int climbStairs(int n) {
if (n < 3) return n;
int[] dp = new int[n + 1];
dp[0] = 1;
for (int j = 0; j <= n; j++) {
for (int i = 1; i < 3; i++) {
if (j >= i) dp[j] += dp[j - i];
}
}
return dp[n];
}
}
1113

被折叠的 条评论
为什么被折叠?



