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();
    }
};
posted @ 2019-09-26 17:50  于老师的父亲王老爷子  阅读(18)  评论(0)    收藏  举报