卡码网46. 携带研究材料
题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。
小明的行李空间为 N
,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。
输入描述
第一行包含两个正整数,第一个整数 M
代表研究材料的种类,第二个正整数 N
,代表小明的行李空间。
第二行包含 M
个正整数,代表每种研究材料的所占空间。
第三行包含 M
个正整数,代表每种研究材料的价值。
输出描述
输出一个整数,代表小明能够携带的研究材料的最大价值。
输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5
提示信息
小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。
数据范围:
1 <= N <= 5000
1 <= M <= 5000
研究材料占用空间和价值都小于等于 1000
题解
标准的 01背包问题 ,动态规划解决。
💡 01背包基础参考:代码随想录-01背包理论基础
dp
数组含义:dp[i][j]
表示从下标0
到i
的物品中选择,放入容量为j
的背包中,所能达到的最大价值- 状态转移方程:
- 如果当前物品的重量/所占空间
weights[i]
大于当前背包容量j
,则不放入,即dp[i][j] = dp[i - 1][j]
- 否则,比较放入物品
i
(可能需要拿出其他物品腾空间)和不放入i
哪个价值更高,即dp[i][j] = max(dp[i - 1][j], dp[i - i][j - weights[i]] + values[i])
- 如果当前物品的重量/所占空间
代码(C++)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int m, n;
std::cin >> m >> n;
std::vector<int> weights(m), values(m);
for (int i = 0; i < m; ++i)
std::cin >> weights[i];
for (int i = 0; i < m; ++i)
std::cin >> values[i];
std::vector<std::vector<int>> dp(m, std::vector<int>(n + 1, 0));
for (int j = weights[0]; j <= n; ++j)
dp[0][j] = values[0];
for (int i = 1; i < m; ++i) {
for (int j = 0; j <= n; ++j) {
if (weights[i] > j)
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = std::max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]);
}
}
std::cout << dp[m - 1][n] << std::endl;
return 0;
}
或者也可以将 dp
数组改为滚动数组,实现数组降维,减小空间开销(参考:代码随想录 )
// 一维dp
std::vector<int> dp(n + 1);
for (int j = weights[0]; j <= n; ++j)
dp[j] = values[0];
for (int i = 1; i < weights.size(); ++i) {
for (int j = n; j >= weights[i]; --j)
dp[j] = std::max(dp[j], dp[j - weights[i]] + values[i]);
}
std::cout << dp[n] << std::endl;
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
题解
根据题目要求,首先不难想到一个基本目标:每个子集的元素之和应该恰为 nums
所有元素之和的 一半 。自然,我们可以先找到这个目标和:
// 先求出每个子集的元素和
int sum = 0;
for (int num : nums)
sum += num;
if (sum % 2 == 1)
return false; // 目标和为奇数肯定无法实现
sum /= 2;
接下来就是看 nums
中能不能找一堆元素“凑”出 sum
。这样考虑似乎和 组合问题类似,于是尝试回溯算法解决:
class Solution // 回溯算法:超时
{
private:
int curSum = 0;
bool flag = false;
void backTracking(const vector<int> &nums, int target, int start) {
// 剪枝
if (curSum > target)
return;
// 递归出口:子集元素和达到目标值
if (curSum == target) {
flag = true;
return;
}
for (int i = start; i < nums.size(); ++i) {
curSum += nums[i]; // 处理
backTracking(nums, target, i + 1); // 递归
curSum -= nums[i]; // 回溯
}
}
public:
bool canPartition(vector<int> &nums)
{
// 先求出每个子集的元素和
int sum = 0;
for (int num : nums)
sum += num;
if (sum % 2 == 1)
return false; // 目标和为奇数肯定无法实现
sum /= 2;
// 回溯算法
sort(nums.begin(), nums.end());
backTracking(nums, sum, 0);
return flag;
}
};
经检验,回溯算法可行但是数据规模大的时候会超时——毕竟回溯本质上是穷举。
于是我们考虑更优雅的算法:动态规划。有一定 01背包问题基础 的话可以发现,本题其实可以转化为一个01背包问题:
能否将一个容量为 sum
的背包,用 weights
和 values
均为 nums
的物品恰好装满?
“恰好装满容量为
sum
的背包” 对应 “找到一个元素和为sum
的子集”,“weights
和values
均为nums
” 对应 “从nums
中取子集”。
那么就按经典的01背包问题套路解决即可:
bool canPartition(vector<int> &nums)
{
// 先求出每个子集的元素和
int sum = 0;
for (int num : nums)
sum += num;
if (sum % 2 == 1)
return false; // 目标和为奇数肯定无法实现
sum /= 2;
// 背包问题:能否将weights和values都为nums、容量为sum的背包恰好装满
vector<vector<int>> dp(nums.size(), vector<int>(sum + 1, 0));
for (int j = nums[0]; j <= sum; ++j)
dp[0][j] = nums[0];
for (int i = 1; i < nums.size(); ++i) {
for (int j = 0; j <= sum; ++j) {
if (nums[i] > j)
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
}
}
return dp[dp.size() - 1][sum] == sum;
}
当然也可以将 dp
数组从二维降为一维( 滚动数组 ):
bool canPartition(vector<int> &nums)
{
// 先求出每个子集的元素和
int sum = 0;
for (int num : nums)
sum += num;
if (sum % 2 == 1)
return false; // 目标和为奇数肯定无法实现
sum /= 2;
// 背包问题:能否将weights和values都为nums、容量为sum的背包恰好装满
vector<int> dp(sum + 1, 0);
for (int j = nums[0]; j <= sum; ++j)
dp[j] = nums[0];
for (int i = 1; i < nums.size(); ++i) {
for (int j = sum; j >= nums[i]; --j) // 注意:一维dp要倒着遍历容量j
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
return dp[sum] == sum;
}