在问 · 算法题解

90. 子集 II

中等回溯排序 + 回溯去重力扣原题 ↗

题目描述

输入整数数组 nums,其中可能有重复元素。

返回 nums 的所有子集(幂集)。由同样的一组数组成的子集只算一个,结果里不能重复。

子集之间的顺序不限。

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

解释:两个 2 是重复元素,[1,2] 和 [2] 各只算一次,共 6 个子集。

示例 2
输入:nums = [0]
输出:[[],[0]]

数据范围

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

解题思路:排序 + 回溯去重

排序后用 dfs(start) 枚举子集:每进入一层先记录当前 path,再从 start 开始挑下一个元素。去重规则:同一层中,如果 nums[i] 与 nums[i-1] 相同且 i > start,就跳过——因为用 nums[i-1] 开头的分支已经覆盖了用 nums[i] 开头的所有情况。

为什么正确

同一层中每个数值最多被选为“下一个元素”一次,且选的是它在 [start, n) 中的第一次出现。

复杂度分析

时间 O(n · 2^n),空间 O(n)(不计输出)。

易错点

写成 i > 0 而不是 i > start 会把 [2,2] 这种纵向使用重复值的子集也剪掉;忘记排序则重复值不相邻,去重失效。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res, path = [], []

        def dfs(start):
            res.append(path[:])
            for i in range(start, len(nums)):
                if i > start and nums[i] == nums[i - 1]:
                    continue
                path.append(nums[i])
                dfs(i + 1)
                path.pop()

        dfs(0)
        return res

载入编辑器,自己跑一遍 →

其他解法

二进制枚举先枚举全部掩码再过滤重复;回溯在同一层跳过相同元素,直接不生成重复子集,重复越多优势越大。两者的去重条件本质相同:相同元素只能按顺序使用。

解法 2:二进制枚举 + 去重规则 时间 O(n·2ⁿ),空间 O(n)

先排序,让相同元素挨在一起。然后像第 78 题一样枚举 0 到 2ⁿ-1 的每个掩码。为了去重,规定一组相同元素只能“从前往后连续地选”:如果选了 nums[i],而 nums[i-1] 与它相等却没被选,这个掩码就和另一个掩码对应同一个子集,直接跳过。

不需要递归,去重规则只是一个局部判断。但它仍会枚举全部 2ⁿ 个掩码,重复元素很多时会做大量无用工作;回溯则在搜索时就把重复分支剪掉了。

from typing import List

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        n = len(nums)
        res = []
        for mask in range(1 << n):
            cur = []
            ok = True
            for i in range(n):
                if mask >> i & 1:
                    # 相同元素只允许“从前往后连续选”,否则会产生重复子集
                    if i > 0 and not (mask >> (i - 1) & 1) and nums[i] == nums[i - 1]:
                        ok = False
                        break
                    cur.append(nums[i])
            if ok:
                res.append(cur)
        return res

回溯 专题的其他题目

查看 回溯 全部题目 →