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;}
};