Day42 | 01背包问题,你该了解这些!, 01背包问题,你该了解这些! 滚动数组, 416. 分割等和子集
其实背包问题画成表格可以更好的理解状态的转移。
分割等和子集
LeetCode题目:https://leetcode.cn/problems/partition-equal-subset-sum/
题目中的背包理解
对题目进行抽象,可以看成是数组中的数值即代表重量,也代表价值。因此所求得题意即可抽象为“能否在背包容积为sum/2时候装满背包?”因此可以确定背包容积的范围最大到达sum/2即可。同时由于数组内容是无序排放,所以数组中的内容应该全部遍历,即物品应该被全部遍历。
二维背包DP思路
按照五步进行分析,dp数组在本题目中的含义为可以取到数组中0-i个位置的数字时,容量为sum/2的背包能否装满。因此可以递推当前第i位置的j容量背包所能承载最大价值为:max(i-1位置容量背包的最大价值,i-1位置背包容量为j-第i个位置物品的容积时背包的最大价值 + 第i个位置的物品价值)。即max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i])。
最后,根据递推公式可以执行正序的遍历。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0 ; i < nums.size() ; i++) {
sum += nums[i];
}
if(sum%2==1) return false;
vector<vector<int>> dp(nums.size(), vector(sum/2 + 1, 0));
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j <= sum/2; j++) {
if (j < nums[i]) {
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]] + nums[i]);
}
}
}
if(dp[nums.size()-1][sum/2]==(sum/2)) return true;
return false;
}
};
一维滚动数组背包DP思路思路
通过以上二维代码理解,可以看出对于当前的dp[i][j],主要依靠dp左侧的元素dp[i-1][j], dp[i-1][j-nums[i]]来决定,即只对上一行的背包容量依赖。因此可以将dp简化为对于容量为j的背包当前最大的价值,来保存当前i的数值应用在下次循环。值得注意的是,当对新的i进行迭代的时候,如果从左到右进行迭代,则会导致i-1位置的元素被覆盖,即后续无法得到正确的值。因此在i内部,背包容量应该倒序迭代。
代码如下:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0 ; i < nums.size() ; i++) {
sum += nums[i];
}
if(sum%2==1) return false;
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] = max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
if(dp[sum/2]==(sum/2)) return true;
return false;
}
};