Files
leetcode-master/problems/0416.分割等和子集.md
youngyangyang04 1e0c5e671b Update
2020-10-12 10:20:23 +08:00

4.8 KiB
Raw Blame History

和Leetcode 473火柴拼正方形和Leetcode 698划分为k个相等的子集是

思路

这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

那么只要找到集合里能够出现 sum / 2 的集合,就算是可以分割成两个相同元素和子集了。

本来是我是想用回溯暴力搜索出所有答案的各种剪枝还是超时了不想在调了放弃回溯直接上01背包吧。

如下的讲解中我讲的重点是如何把01背包应用到此题而不是讲01背包如果对01背包本身还不理解的同学需要额外学习一下基础知识我后面也会在代码随想录里深度讲解背包问题。

背包问题

背包问题大家都知道就是书包书包可以容纳的体积n 然后有各种商品每一种商品体积为m价值为z问如果把书包塞满不一定能塞满书包里的商品最大价值总和是多少。

背包问题有多种背包方式常见的有01背包、完全背包、多重背包、分组背包和混合背包等等。

要注意背包问题问题中商品是不是可以重复放入。

即一个商品如果可以重复多次放入是完全背包而只能放入一次是01背包写法还是不一样的。

要明确本题中我们要使用的是01背包因为元素我们只能用一次。

为了让大家对背包问题有一个整体的了解,可以看如下图:

回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。

那么来一一对应一下本题,看看背包问题如果来解决。

只有确定了如下四点,才能把背包问题,套到本题上来。

  • 背包的体积为sum / 2
  • 背包要放入的商品(集合里的元素)体积为 元素的数值,价值也为元素的数值
  • 背包如何正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素一定是不可重复放入。

定义里数组为dp[]dp[i] 表示 背包中放入体积为i的商品最大价值为dp[i]。

套到本题dp[i]表示 背包中总和是i最大可以凑成总和为i的元素总和为dp[i]。

dp[i]一定是小于等于i的因为背包不能装入超过自身体积的商品这里理解为元素数值

如果dp[i] == i 说明集合中的元素正好可以凑成总和i理解这一点很重要。

C++代码如下(详细注释

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;

        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100数组的大小不会超过 200
        // 那么背包内总和不会大于20000所以定义一个20000大的数组。
        vector<int> dp(20001, 0); 
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        if (sum % 2 == 1) return false;
        int target = sum / 2;

        // 开始 01背包 
        for(int i = 0; i < nums.size(); i++) {
            for(int j = target; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        // 集合中的元素正好可以凑成总和target 
        if (dp[target] == target) return true;
        return false;      
    }
};

暴力

本来是想用回溯暴力搜索出所有答案的各种剪枝还是超时了不想在调了放弃回溯直接上01背包吧。

回溯搜索超时的代码如下:

class Solution {
private:
    int target;
    bool backtracking(vector<int>& nums, int startIndex, int pathSum, vector<bool>& used) {
        for (int i = startIndex; i < nums.size(); i++) {
            if (pathSum > target) break; // 剪枝
            if (target < nums[i]) break; // 剪枝 
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { // 去重
                continue;
            }
            pathSum += nums[i];
            used[i] = true;
            if (pathSum == target) return true;
            if (backtracking(nums, i + 1, pathSum, used)) return true;
            used[i] = false;
            pathSum -= nums[i];
        }
        return false;
    }

public:
    bool canPartition(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (sum % 2 == 1) return false;
        target = sum / 2;
        cout << "sum:" << sum << " target:" << target << endl;
        return backtracking(nums, 0, 0, used);
    }
};