加载中...

daily leetcode - subsets-ii - !

题目地址

https://leetcode.com/problems/subsets-ii/

题目描述

Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

Input: [1,2,2]
Output:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

思路

这道子集合之二是之前那道 Subsets 的延伸,这次输入数组允许有重复项,其他条件都不变,只需要在之前那道题解法的基础上稍加改动便可以做出来,我们先来看非递归解法,拿题目中的例子 [1 2 2] 来分析,根据之前 Subsets 里的分析可知,当处理到第一个 2 时,此时的子集合为 [], [1], [2], [1, 2],而这时再处理第二个 2 时,如果在 [] 和 [1] 后直接加 2 会产生重复,所以只能在上一个循环生成的后两个子集合后面加 2,发现了这一点,题目就可以做了,我们用 last 来记录上一个处理的数字,然后判定当前的数字和上面的是否相同,若不同,则循环还是从 0 到当前子集的个数,若相同,则新子集个数减去之前循环时子集的个数当做起点来循环,这样就不会产生重复了,代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int> &S) {
        if (S.empty()) return {};
        vector<vector<int>> res(1);
        sort(S.begin(), S.end());
        int size = 1, last = S[0];
        for (int i = 0; i < S.size(); ++i) {
            if (last != S[i]) {
                last = S[i];
                size = res.size();
            }
            int newSize = res.size();
            for (int j = newSize - size; j < newSize; ++j) {
                res.push_back(res[j]);
                res.back().push_back(S[i]);
            }
        }
        return res;
    }
};

整个添加的顺序为:

[]
[1]
[2]
[1 2]
[2 2]
[1 2 2]

对于递归的解法,根据之前 Subsets 里的构建树的方法,在处理到第二个 2 时,由于前面已经处理了一次 2,这次我们只在添加过 2 的 [2] 和 [1 2] 后面添加 2,其他的都不添加,那么这样构成的二叉树如下图所示:

                        []        
                   /          \        
                  /            \     
                 /              \
              [1]                []
           /       \           /    \
          /         \         /      \        
       [1 2]       [1]       [2]     []
      /     \     /   \     /   \    / \
  [1 2 2] [1 2]  X   [1]  [2 2] [2] X  []

代码只需在原有的基础上增加一句话,while (S[i] == S[i + 1]) ++i; 这句话的作用是跳过树中为 X 的叶节点,因为它们是重复的子集,应被抛弃。代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int> &S) {
        if (S.empty()) return {};
        vector<vector<int>> res;
        vector<int> out;
        sort(S.begin(), S.end());
        getSubsets(S, 0, out, res);
        return res;
    }
    void getSubsets(vector<int> &S, int pos, vector<int> &out, vector<vector<int>> &res) {
        res.push_back(out);
        for (int i = pos; i < S.size(); ++i) {
            out.push_back(S[i]);
            getSubsets(S, i + 1, out, res);
            out.pop_back();
            while (i + 1 < S.size() && S[i] == S[i + 1]) ++i;
        }
    }
};

整个添加的顺序为:

[]
[1]
[1 2]
[1 2 2]
[2]
[2 2]

思路 2

这道题目是求集合,并不是 求极值,因此动态规划不是特别切合,因此我们需要考虑别的方法。

这种题目其实有一个通用的解法,就是回溯法。
网上也有大神给出了这种回溯法解题的
通用写法,这里的所有的解法使用通用方法解答。
除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。

我们先来看下通用解法的解题思路,我画了一张图:

mark

通用写法的具体代码见下方代码区。

关键点解析

  • 回溯法
  • backtrack 解题公式

代码

  • 语言支持:JS,C++,Python3

JavaScript Code:



/*
 * @lc app=leetcode id=90 lang=javascript
 *
 * [90] Subsets II
 *
 * https://leetcode.com/problems/subsets-ii/description/
 *
 * algorithms
 * Medium (41.53%)
 * Total Accepted:    197.1K
 * Total Submissions: 469.1K
 * Testcase Example:  '[1,2,2]'
 *
 * Given a collection of integers that might contain duplicates, nums, return
 * all possible subsets (the power set).
 * 
 * Note: The solution set must not contain duplicate subsets.
 * 
 * Example:
 * 
 * 
 * Input: [1,2,2]
 * Output:
 * [
 * ⁠ [2],
 * ⁠ [1],
 * ⁠ [1,2,2],
 * ⁠ [2,2],
 * ⁠ [1,2],
 * ⁠ []
 * ]
 * 
 * 
 */
function backtrack(list, tempList, nums, start) {
    list.push([...tempList]);
    for(let i = start; i < nums.length; i++) {
        // 和78.subsets的区别在于这道题nums可以有重复
        // 因此需要过滤这种情况
        if (i > start && nums[i] === nums[i - 1]) continue;
        tempList.push(nums[i]);
        backtrack(list, tempList, nums, i + 1)
        tempList.pop();
    }
}
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsetsWithDup = function(nums) {
    const list = [];
    backtrack(list, [], nums.sort((a, b) => a - b), 0, [])
    return list;
};

C++ Code:

class Solution {
private:
    void subsetsWithDup(vector<int>& nums, size_t start, vector<int>& tmp, vector<vector<int>>& res) {
        res.push_back(tmp);
        for (auto i = start; i < nums.size(); ++i) {
            if (i > start && nums[i] == nums[i - 1]) continue;
            tmp.push_back(nums[i]);
            subsetsWithDup(nums, i + 1, tmp, res);
            tmp.pop_back();
        }
    }
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        auto tmp = vector<int>();
        auto res = vector<vector<int>>();
        sort(nums.begin(), nums.end());
        subsetsWithDup(nums, 0, tmp, res);
        return res;
    }
};

Python Code:

class Solution:
    def subsetsWithDup(self, nums: List[int], sorted: bool=False) -> List[List[int]]:
        """回溯法,通过排序参数避免重复排序"""
        if not nums:
            return [[]]
        elif len(nums) == 1:
            return [[], nums]
        else:
            # 先排序,以便去重
            # 注意,这道题排序花的时间比较多
            # 因此,增加一个参数,使后续过程不用重复排序,可以大幅提高时间效率
            if not sorted:
                nums.sort()
            # 回溯法
            pre_lists = self.subsetsWithDup(nums[:-1], sorted=True)
            all_lists = [i+[nums[-1]] for i in pre_lists] + pre_lists
            # 去重
            result = []
            for i in all_lists:
                if i not in result:
                    result.append(i)
            return result

本文参考自:
https://github.com/grandyang/leetcode/ &
https://github.com/azl397985856/leetcode


标题: daily leetcode - subsets-ii - !
文章作者: lonuslan
文章链接: https://www.lonuslan.com/articles/2020/03/07/1583561364431.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hi I'm LonusLan 👋🏼

评论