473. Matchsticks to Square & 698. Partition to K Equal Sum Subsets & 416. Partition Equal Subset Sum

这题给我的教训很多。
先上自己的错误代码:
class Solution {
public:
bool makesquare(vector<int>& nums) {
int sz = nums.size();
if (sz < 4)
return false;
int sum = 0, maxLen = 0;
for (int num : nums) {
sum += num;
maxLen = max(maxLen, num);
}
if (sum % 4)
return false;
int sideLen = sum / 4;
if (maxLen > sideLen)
return false;
vector<int> sums(4, 0);
return dfs(nums, 0, sums, sideLen);
}
private:
bool dfs(vector<int>& nums, int idx, vector<int>& sums, int target) {
if (idx == nums.size()) {
if (sums[0] == target && sums[1] == target && sums[2] == target)
return true;
else
return false;
}
for (int i = 0; i < 4; ++i) {
if (sums[i] + nums[i] > target)
continue;
sums[i] += nums[idx];
if (dfs(nums, idx+1, sums, target))
return true;
sums[i] -= nums[idx];
}
return false;
}
};
//错的原因是:这里的思路是:只有有一个组合是可以组成一个边的,那么这个组合一定是最终答案中的一个。可是这样是错误的,比如:1,3和其他的组合可以组成一条边,但是4可以替代它们,而且它们可能在最终的答案里面不是在一起的,所以这个可能是错误的。
//思维局限的原因可能是:习惯了“一个一个”地进行dfs,然后进行轮流的四次dfs。
//这里的解法是每个数,轮流放在四个篮子里面,看看是不是合适。
//回溯的方法就是实验每一种可能性。
错误的原因写出来了。错误原因一是上面的这个,而也是没有几个一起dfs,一个不行放到另一个的思路。之后应该会好很多。
然后正确的做法:
class Solution {
public:
bool makesquare(vector<int>& nums) {
int sz = nums.size();
if (sz < 4)
return false;
int sum = 0, maxLen = 0;
for (int num : nums) {
sum += num;
maxLen = max(maxLen, num);
}
if (sum % 4)
return false;
int sideLen = sum / 4;
if (maxLen > sideLen)
return false;
sort(nums.begin(), nums.end());//优化1
reverse(nums.begin(), nums.end());
vector<int> sums(4, 0);
return dfs(nums, 0, sums, sideLen);
}
private:
bool dfs(vector<int>& nums, int idx, vector<int>& sums, int target) {
if (idx == nums.size()) {
/*if (sums[0] == target && sums[1] == target && sums[2] == target)
return true;
else
return false;*/
return true;
}
for (int i = 0; i < 4; ++i) {
if (sums[i] + nums[idx] > target)
continue;
int j = i;
while (--j >= 0)//优化2
if (sums[j] == sums[i])
break;
if (j != -1)
continue;
sums[i] += nums[idx];
if (dfs(nums, idx+1, sums, target))
return true;
sums[i] -= nums[idx];
}
return false;
}
};
1.注意这里几个一起进行dfs的方法。每次还是前进一个idx,这是不能变的。
2.第一个优化倒序操作。没有的话会超时,加上了效率提升很多。我不是完全理解为什么会这样,这里尝试解释一下:
尽早把长的分配掉。如果把短的早点分配掉的话,长的留在后面,有很多可能性到了后面才返回false。把长的放在前面,能够提前返回false。相当于剪枝了。其实就是为了提前触发不合理的分配方式,使之在第32行提前返回
3.第二个优化:对于当前这个火柴,如果前面已经有相同的长度尝试过这个火柴了,那么不需要再尝试了。对于每一根火柴,只是为了寻找合适的group而已。
另外,相同的一道题目:

使用优化的效果相当明显,由超时->60ms->4ms。
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
if (k < 2)
return true;
int maxNum = INT_MIN, sumAll = 0;
for (int num : nums) {
maxNum = max(maxNum, num);
sumAll += num;
}
if (sumAll % k != 0)
return false;
int target = sumAll / k;
vector<int> sums(k, 0);
sort(nums.begin(), nums.end());
reverse(nums.begin(), nums.end());
return dfs(nums, 0, sums, target);
}
private:
bool dfs(vector<int>& nums, int idx, vector<int>& sums, int target) {
if (idx == nums.size())
return true;
for (int i = 0; i < sums.size(); ++i) {
if (sums[i] + nums[idx] > target)
continue;
int j = i;
while (--j >= 0)
if (sums[i] == sums[j])
break;
if (j != -1)
continue;
sums[i] += nums[idx];
if (dfs(nums, idx+1, sums, target))
return true;
sums[i] -= nums[idx];
}
return false;
}
};
然后,可以使用上面的代码来解决下面这道题目:

值得注意的是,还可以使用背包:是不是可以挑选出一部分组成和为target?特征是用值作为维度。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum & 0x1)
return false;
int sz = nums.size();
int target = sum / 2;
vector<vector<bool>> dp(sz+1, vector<bool>(target+1, false));
for (int i = 0; i <= sz; ++i)
dp[i][0] = true;
for (int i = 1; i <= sz; ++i)
for (int j = 1; j <= target; ++j) {
if (nums[i-1] > j)
dp[i][j] = dp[i-1][j];
else
dp[i][j] = dp[i-1][j-nums[i-1]] || dp[i-1][j];
}
return dp.back().back();
}
};
浙公网安备 33010602011771号