子集问题,一定要考虑到背包问题,也就是动态规划。
最后还可以进行状态压缩,简称状压dp
这句话很重要,如果你要用多次的话,就不要反向遍历。相当于对每一层反向填表。
最后主要是掌握!二维dp的使用
class Solution {
public:
bool canPartition(vector<int>& nums) {
//只要是取或者不取就直接背包问题
int sum = 0;
for(auto x:nums) sum+=x;
if(sum%2!=0) return false;//如果数组和是奇数就一定不能分成两个相等的子集
//这其实也就是分成了两个背包,一个背包能装满sum/2,另一个背包一定可以
//dp[i][j]代表了对于前i个物品,当背包容量为j时恰好可以装满为true;不能恰好就false
//比如说,如果 dp[4][9] = true,其含义为:对于容量为 9 的背包,若只是用前 4 个物品,可以有一种方法把背包恰好装满。所以我们最后要求的就是dp[n][sum/2]
//初始化dp[i][0]无需装东西,都是true;dp[0][i]没东西装,都是false
// vector<vector<bool>> dp(nums.size()+1,vector<bool>(sum/2+1));
// for(int i = 0; i <= nums.size(); ++i){
// dp[i][0] = true;
// }
// //状态转移方程是根据选择来的。dp[i][j]如果没有选择第i个物品,那么dp[i][j] = dp[i-1][j];就是继承上一个状态
// //如果选择了dp[i][j] = dp[i-1][j-nums[i]];也就是j-nums[i]的重量可以恰好装满,那j的重量在加上nums[i]后也可以恰好装满,但是i从1开始,所以是nums[i-1]
// for(int i = 1; i <= nums.size(); ++i){
// for(int j = 1; j <= sum/2; ++j){
// //此时考虑背包容量是否足够,那只能不装i了
// if(j - nums[i-1] < 0){
// dp[i][j] = dp[i-1][j];
// }
// else{
// //取或者不取只要有一种情况是对的,那就是对的
// dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
// }
// }
// }
// return dp[nums.size()][sum/2];
//我们发现dp[i][..]之和dp[i-1][..]的有关系,所以我们采用状压dp,只要记录dp[i-1][...]的值即可
vector<bool> dp(sum/2+1,false);
dp[0] = true;//如果取总量为0的话,不管怎么取都可以
//dp[i]记录的是取到总量为i时,是否可以正常取到
for(int i = 0; i < nums.size(); ++i){
for(int j = sum/2; j >= 0; --j){
if(j - nums[i] >= 0){
//如果可以取就是两种情况
dp[j] = dp[j] || dp[j-nums[i]];
}
}
}
return dp[sum/2];
}
};
可以dfs,也可以dp。
就是01背包,取或者不取,直接先上二维dp,dp[i][j]代表的是前i个数字是否可以组成总和j。
状态转移方程:如果取了当前物品会超过重量j,就让dp[i][j]继承dp[i-1][j]就是不取这个物品
如果没超过,就判断取当前这个物品或者不取有一个是对的就行了。
空间压缩:状压dp,只和上一层有关系,但是要从后往前,因为不可以重复取,只要上一层取或者没取的结果。
class Solution {
public:
// bool dfs(int index, vector<int>& nums, int tmp,int sum){
// //不用考虑越界,用for循环控制
// if(tmp > sum) return false;
// if(tmp == sum) return true;
// for(int i = index; i < nums.size();++i){
// if(dfs(i+1,nums,tmp+nums[i],sum)) return true;
// }
// return false;
// }
bool canPartition(vector<int>& nums) {
//分割子集问题
int sum = 0;
for(int x : nums){
sum += x;
}
if(sum % 2 != 0) return false;
//先试试dfs,就是当前数字选或者不选,当越界就false,和为sum/2就true
// return dfs(0,nums,0,sum/2);
//背包问题,就是取或者不取的问题,背包问题一定是二维dp,先写出二维,再压缩
//dp[i][j]=true代表的是取前i件物品有一种情况可以把重量为j的背包刚好填满,最后求的是dp[n][sum/2]
//状态转移方程:如果取了当前物品会超过重量j,就让dp[i][j]继承dp[i-1][j]就是不取这个物品
//如果没超过,就判断取当前这个物品或者不取有一个是对的就行了
//就是dp[i-1][j] || dp[i-1][j-nums[i-1]]
//basecase:
// vector<vector<bool>> dp(nums.size()+1,vector<int>(sum/2+1));
// //用来装大小为0的背包都是true
// for(int i = 0; i <= nums.size(); ++i){
// dp[i][0] = true;
// }
// for(int i = 1; i <= nums.size(); ++i){
// for(int j = 1; j <= sum/2; ++j){
// if(j < nums[i-1]){
// dp[i][j] = dp[i-1][j];
// }
// else{
// dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
// }
// }
// }
// return dp[nums.size()][sum/2];
//状压dp,只和上一层有关系,但是要从后往前
vector<bool> dp(sum/2+1);
//basecase
dp[0] = true;
for(int i =1; i <= nums.size(); ++i){
for(int j = sum/2; j >= 1; --j){
if(j < nums[i-1]){
dp[j] = dp[j];
}
else{
dp[j] = dp[j] || dp[j-nums[i-1]];
}
}
}
return dp[sum/2];
}
};