代码随想录算法训练营第三十五天(20250303) |01背包问题 二维,01背包问题 一维,416. 分割等和子集 -[补卡20250316]
01背包问题 二维
链接
- 遍历物品没有大小顺序要求
- 重点是模拟,推导出递推公式
#include <iostream>
#include <vector>
int main(){
int m, n;
std::cin>>m>>n;
std::vector<int> weight(m,0),value(m,0);
for(int i{0}; i<m; i++){
std::cin>>weight[i];
}
for(int i{0}; i<m; i++){
std::cin>>value[i];
}
std::vector<std::vector<int>> dp(m, std::vector<int>(n+1, 0));
for(int i{0}; i<=n; i++){
dp[0][i] = i>=weight[0]?value[0]:0;
}
for(int i{1}; i<m; i++){
for(int j{0}; j<=n; j++){
if(weight[i]<=j){
dp[i][j] = std::max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
std::cout<<dp[m-1][n]<<std::endl;
//
return 0;
}
01背包问题 一维
链接
- 01背包问题可以用一维数组处理,但是需要注意遍历背包的顺序,应该从大到小遍历,因为遍历判断是否应该放入当前物品时,需要比较当前物品放入后剩余空间的最大价值,此时如果当前背包容量大于当前物品空间的两倍,则正序遍历时会将其放入两次,例:dp[j] = max(dp[j], dp[j-weight[i]+value[i]),若当前背包容量为5,物品重量为2,物品价值特别大,则比较未放入当前物品时的价值与背包容量为3时的价值与当前物品价值的和,这里假如是从小到大遍历,则在判断容量为3时,因为当前物品价值特别大,已经放入,会与当前再次放入矛盾,导致错误,所以应该从大到小遍历,确保比较时当前物品没有被放入比较过
#include <iostream>
#include <vector>
int main(){
int m, n;
std::cin>>m>>n;
std::vector<int> weight(m,0),value(m,0);
for(int i{0}; i<m; i++){
std::cin>>weight[i];
}
for(int i{0}; i<m; i++){
std::cin>>value[i];
}
//
std::vector<int> dp(n+1,0);
for(int i{0}; i<m; i++){
for(int j{n}; j>=weight[i]; j--){
dp[j] = std::max(dp[j], dp[j-weight[i]]+value[i]);
}
}
std::cout<<dp[n]<<std::endl;
return 0;
}
416. 分割等和子集
- 本质上也是01背包问题,相当于每个数字是一个物品,价值和体积都等于数字本身,有一个总和一半大小的背包,要求背包放入物品的价值总和尽量最大,最大是sum/2,如果能达到sum/2,说明可以划分为两个相同大小的子集
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum{0};
for(const auto& val:nums){
sum += val;
}
if(sum%2!=0){
return false;
}
//
std::vector<int> dp(sum/2+1,0);
for(int i{0}; i<nums.size(); i++){
for(int j{sum/2}; j>=nums[i]; j--){
dp[j] = std::max(dp[j], dp[j-nums[i]]+nums[i]);
}
}
return dp[sum/2]==sum/2;
}
};