分割等和子集
对于这道题,我的第一想法是回溯,但是经过运行后,发现超时了,经过剪枝后还是超时了,所以大家不要尝试了,因为回溯的时间复杂度是2^n,不如直接用动态规划。
我们将这个问题视为背包问题,背包的大小无限大,物品的价值与大小均是这个nums对应的数值.物品的总价值为tars,如果tars不是2的倍数,说明一定不可以分,令tar=tars/2,即总容量的一半。
如果最后求得某一行背包内的价值等于了这些物品的价值,就说明背包内刚好可以装下价值为总价值一半的物品,也就是可以等和分割,也就是有解,返回true。
二维解法
我们将这个集合视为一个背包,因为,我们无需考虑背包总容量大于tar的情况,因此,我们初试化dp数组的行数为物品的数量,列数为tar+1,初始化,第一行的值,小于nums[0]的位置值为0,大于等于nums[0]的位置,值为nums[0]。
求背包问题,我们先对物品变量,再对价值遍历(二维数组对遍历顺序没有要求)。
对于dp[ii][jj]。我们令其的值dp[ii][jj] = max(dp[ii-1][jj],dp[ii-1][jj-nums[ii]]+nums[ii]),跟背包问题的递推式子相同。
但是需要考虑jj<nums[ii]的情况,因为此时是越界的,因此,我们直接copy上一层的元素即可。
代码
class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0;
for(int ii =0;ii<nums.size();ii++)
{
tar +=nums[ii];
}
if(tar%2==1) return false;
tar/=2;
vector<vector<int>> dp(nums.size(),vector<int>(tar+1,0));
for(int ii=nums[0];ii<dp[0].size();ii++)
dp[0][ii] = nums[0];
for(int ii =1;ii<nums.size();ii++)
{
for(int jj =0;jj<dp[0].size();jj++)
{
if(jj<=nums[ii])
dp[ii][jj] = dp[ii-1][jj];
else
dp[ii][jj] = max(dp[ii-1][jj],dp[ii-1][jj-nums[ii]]+nums[ii]);
}
if(dp[ii][tar]==tar) return true;
}
return false;
}
};
一维解法
对于一维的解法,显然会在空间复杂度上优于二维解法,但是一维的解法只能从后向前遍历,因为,后面的元素需要上一层前面的值,如果从前向后遍历,遍历到一个元素时,前面的值可能已经被修改,此时就无法得到正确的值。
代码
class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0;
for(int ii =0;ii<nums.size();ii++)
{
tar +=nums[ii];
}
if(tar%2==1) return false;
tar/=2;
vector<int>dp(tar+1,0);
for(int ii =nums[0];ii<dp.size();ii++)
dp[ii] = nums[0];
for(int ii =1;ii<nums.size();ii++)
{
for(int jj =dp.size()-1;jj>=nums[ii];jj--)
dp[jj] = max(dp[jj],dp[jj-nums[ii]]+nums[ii]);
if(dp[tar] == tar) return true;
}
return false;
}
};