【做题也是一场游戏】90. 子集 II

84 阅读2分钟

题目地址

leetcode-cn.com/problems/su…

题目描述

给你一个整数数组 nums ,其中可能 包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

1 <= nums.length <= 10
-10 <= nums[i] <= 10

题解

  • 数在或者不在
  • 有重复的元素,需要判断是否重复,避免重复遍历
  • 先排序,将重复的数据放在一块,便于重复的判断

bit位表示在或不在

class Solution {
    List<Integer> t = new ArrayList<Integer>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        for (int mask = 0; mask < (1 << n); ++mask) {
            t.clear();
            boolean flag = true;
            for (int i = 0; i < n; ++i) {
                if ((mask & (1 << i)) != 0) {
                    if (i > 0 && (mask >> (i - 1) & 1) == 0 && nums[i] == nums[i - 1]) {
                        flag = false;
                        break;
                    }
                    t.add(nums[i]);
                }
            }
            if (flag) {
                ans.add(new ArrayList<Integer>(t));
            }
        }
        return ans;
    }
}

复杂度分析

  • 时间复杂度:O(n×2n)O(n \times 2^n),其中 nn 是数组 nums\textit{nums} 的长度。排序的时间复杂度为 O(nlogn)O(n \log n)。一共 2n2^n个状态,每种状态需要 O(n)O(n) 的时间来构造子集,一共需要 O(n×2n)O(n \times 2^n) 的时间来构造子集。由于在渐进意义上 O(nlogn)O(n \log n) 小于 O(n×2n)O(n \times 2^n),故总的时间复杂度为 O(n×2n)O(n \times 2^n)

  • 空间复杂度:O(n)O(n)。即构造子集使用的临时数组 tt 的空间代价。

递归

class Solution {
    List<Integer> t = new ArrayList<Integer>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        dfs(false, 0, nums);
        return ans;
    }

    public void dfs(boolean choosePre, int cur, int[] nums) {
        if (cur == nums.length) {
            ans.add(new ArrayList<Integer>(t));
            return;
        }
        dfs(false, cur + 1, nums);
        if (!choosePre && cur > 0 && nums[cur - 1] == nums[cur]) {
            return;
        }
        t.add(nums[cur]);
        dfs(true, cur + 1, nums);
        t.remove(t.size() - 1);
    }
}

复杂度分析

  • 时间复杂度:O(n×2n)O(n \times 2^n),其中 nn 是数组 nums\textit{nums} 的长度。排序的时间复杂度为 O(nlogn)O(n \log n)。最坏情况下 nums\textit{nums} 中无重复元素,需要枚举其所有 2n2^n个子集,每个子集加入答案时需要拷贝一份,耗时 O(n)O(n),一共需要 O(n×2n)+O(n)=O(n×2n)O(n \times 2^n)+O(n)=O(n \times 2^n)的时间来构造子集。由于在渐进意义上 O(nlogn)O(n \log n) 小于 O(n×2n)O(n \times 2^n),故总的时间复杂度为 O(n×2n)O(n \times 2^n)

  • 空间复杂度:O(n)O(n)。临时数组 t\textit{t} 的空间代价是 O(n)O(n),递归时栈空间的代价为 O(n)O(n)

迭代

重复元素不需要从0位置开始添加,只需要从上个相同元素开始加入的位置开始迭代就行,这里用 Map 记录下位置

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {

        Arrays.sort(nums);

        Map<Integer, Integer> preLocationMap = new HashMap<>();
        List<List<Integer>> result = new ArrayList<>();
        result.add(new ArrayList<>());
        for (Integer num : nums) {
            int location = preLocationMap.getOrDefault(num, -1);
            int start = location == -1 ? 0 : location;
            int size = result.size();
            for (int i = start; i < size; i++) {
                List<Integer> temp = new ArrayList<>(result.get(i));
                temp.add(num);
                result.add(temp);
            }
            preLocationMap.put(num, size);
        }

        return result;
    }
}

复杂度分析

  • 时间复杂度:O(n×2n)O(n \times 2^n)

  • 空间复杂度:O(n)O(n)。临时数组加 map 总共是 O(2n)O(2n)