46. 携带研究材料(第六期模拟笔试)(不在力扣上)
题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。
输入
第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。 第二行包含 M 个正整数,代表每种研究材料的所占空间。 第三行包含 M 个正整数,代表每种研究材料的价值。
输出
输出一个整数,代表小明能够携带的研究材料的最大价值。
思路
这个问题是典型的 0-1 背包问题,可以使用动态规划来解决。
第一步:确定dp数组和下标的含义
定义一个一维数组 dp,其中 dp[i] 表示小明在行李箱容量为 i 的情况下能携带的研究材料的最大价值。
第二步:确定递推公式
对于每种研究材料 m(占据空间为 space[m],价值为 value[m]),小明有两种选择:带或不带。
- 如果不带,则
dp[i]不变。 - 如果带,则
dp[i]可能会更新为dp[i - space[m]] + value[m]。
因此,递推公式为:
第三步:dp数组如何初始化
行李箱容量为 0 时,能携带的研究材料价值为 0,所以 dp[0] = 0。由于我们考虑不带任何物品的情况,所以其他 dp[i] 初始值也应为 0。
第四步:确定遍历顺序
首先遍历所有的研究材料,然后对每种材料,从大到小遍历 dp[i],以确保每种研究材料只被考虑一次。
第五步:举例推导dp数组
假设小明的行李空间为 1,有 6 种研究材料,空间分别为 [2, 2, 3, 1, 5, 2],价值分别为 [2, 3, 1, 5, 4, 3]。
初始化 dp = [0, 0](长度为小明行李空间 + 1)。
遍历所有研究材料:
- 第一种材料(空间 2,价值 2):由于空间大于行李空间,跳过。
- 第二种材料(空间 2,价值 3):同上,跳过。
- 第三种材料(空间 3,价值 1):同上,跳过。
- 第四种材料(空间 1,价值 5):
dp[1] = max(dp[1], dp[1-1] + 5) = max(0, 0 + 5) = 5 - 第五种材料(空间 5,价值 4):同上,跳过。
- 第六种材料(空间 2,价值 3):同上,跳过。
最终结果是 dp[1] = 5。
题解
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int M, N;
std::cin >> M >> N; // 输入研究材料的种类和小明的行李空间
std::vector<int> spaces(M), values(M);
for (int i = 0; i < M; ++i) {
std::cin >> spaces[i]; // 输入每种研究材料的所占空间
}
for (int i = 0; i < M; ++i) {
std::cin >> values[i]; // 输入每种研究材料的价值
}
std::vector<int> dp(N + 1, 0); // 初始化动态规划数组
// 动态规划计算
for (int m = 0; m < M; ++m) {
for (int i = N; i >= spaces[m]; --i) {
dp[i] = std::max(dp[i], dp[i - spaces[m]] + values[m]);
}
}
std::cout << dp[N] << std::endl; // 输出小明能够携带的研究材料的最大价值
return 0;
}
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1: 输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。 示例 2: 输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
提示:
- 1 <= nums.length <= 200
- 1 <= nums[i] <= 100
思路
这个问题可以转换为一个标准的 0-1 背包问题。等同于寻找一个子集,使其元素之和等于整个数组元素之和的一半。
第一步:确定dp数组和下标的含义
定义一个一维数组 dp,其中 dp[j] 表示是否存在一个子集,使得该子集的元素之和为 j。
第二步:确定递推公式
对于每个数字 num,我们有两个选择:加入子集或不加入。
- 如果不加入,则
dp[j]保持不变。 - 如果加入,则
dp[j]可能会变成true,即dp[j] = dp[j] || dp[j - num]。
第三步:dp数组如何初始化
dp[0] 应该初始化为 true,表示存在一个空子集,其和为 0。其他 dp[j] 应该初始化为 false。
第四步:确定遍历顺序
首先遍历所有数字,然后对每个数字,从目标值向 0 遍历 dp[j]。
第五步:举例推导dp数组
假设 nums = [1, 5, 11, 5],则 sum(nums) = 22,目标值 target = sum(nums) / 2 = 11。
初始化 dp = [true, false, ..., false](长度为 target + 1)。
- 第一个数字(1):
dp[11] = dp[11] || dp[10] = false || false = false - 第二个数字(5):
dp[11] = dp[11] || dp[6] = false || false = false - 第三个数字(11):
dp[11] = dp[11] || dp[0] = false || true = true - 第四个数字(5):
dp[11]已经是true,所以不需要改变。
最终结果是 dp[11] = true。
题解
class Solution {
public:
bool canPartition(std::vector<int>& nums) {
int sum = std::accumulate(nums.begin(), nums.end(), 0);
if (sum % 2 != 0) return false;
int target = sum / 2;
std::vector<bool> dp(target + 1, false);
dp[0] = true;
for (int num : nums) {
for (int j = target; j >= num; --j) {
dp[j] = dp[j] || dp[j - num];
}
}
return dp[target];
}
};