跳到正文
第 78 题中等

子集

Subsets

返回数组的所有子集(幂集),不能有重复子集。

在力扣中练习

题目描述

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

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

 

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

解题思路

主解法

回溯 · 每个节点收集

先收当前 path,再决定下一个选谁

子集问题可以看成一棵决策树:每个节点都代表一个已经做出的选择,因此每个节点都是一个合法子集,而不是只有叶子。

为了不重复,递归时带一个 start,下一层只能从当前下标之后选。这样 [2,5] 和 [5,2] 只会出现一次,也不需要额外去重。

分步图解

空集也是子集;从 start 开始,每次选一个数并只往后递归,顺序不同的重复子集就不会出现。

候选 · 只能从 start 往后选
0
1
2
2
5
7
res
[ ]
当前 path
空
path 中的元素就是当前子集;每进入一个节点先收集,再继续选。
1 / 4
每到一个节点先收集当前 path,再决定下一个选谁;空集在根节点就被收集。

自拟数组 [2,5,7] 三个元素,共 2³ = 8 个子集。画面上 array 的灰色部分是已经不能选的下标,path 是当前子集,res 每进入一个节点就追加一次。

分步图解

到达一个搜索节点,当前 path [2, 5, 7] 就是一个子集,复制进 res。

res.size
4
候选 · 只能从 start 往后选
0
1
2
2
5
7
i
res
[][2][2,5][2,5,7]
当前 path
2
5
7
path 中的元素就是当前子集;每进入一个节点先收集,再继续选。
1 / 4
选完 2 后只能从 5、7 继续;[2,5,7] 收集完回退,再尝试 [2,7]。

每个元素在每条分支上只有“选 / 不选”两种状态,时间 O(n·2ⁿ)(复制结果占主导),递归深度 O(n)。

时间 O(n·2ⁿ)空间 O(n)

易错点

  • 只在叶子收集:会漏掉所有中间子集,包括空集。
  • 下一层从 0 开始:会出现 [2,5] 与 [5,2] 等重复排列。
  • 忘记 path.pop_back():不同分支互相污染。
对照解法

二进制掩码 · 枚举

用 n 位二进制表示每个元素选不选

实现最短,但必须能按下标访问;结果数量仍是 2ⁿ,适合 n 很小的场合。

n 个元素,每个元素有“选/不选”两种状态,正好对应一个 n 位二进制数。枚举 0 .. 2ⁿ-1,第 i 位为 1 就把 nums[i] 放进当前子集。

它不需要递归,也不会产生重复;缺点是必须一次性知道 n,而且结果规模固定为 2ⁿ。

当 n 超过 20 左右时,2ⁿ 已经无法承受,这时应该考虑题目是否真的需要枚举所有子集。

  1. 1枚举 mask 从 0 到 2ⁿ-1。
  2. 2检查每一位是否为 1。
  3. 3把对应元素加入当前子集。
  4. 4收集当前子集。
时间 O(n·2ⁿ)空间 O(n)

易错点

  • 1 << n 用 int:n 较大时溢出,必要时用 long long。
  • 位序与数组下标对应错:结果顺序不同但仍合法,需确认题目要求。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入nums = [2,5,7]

空集也是子集;从 start 开始,每次选一个数并只往后递归,顺序不同的重复子集就不会出现。

候选 · 只能从 start 往后选
0
1
2
2
5
7
res
[ ]
当前 path
空
path 中的元素就是当前子集;每进入一个节点先收集,再继续选。
1 / 10
查看 C++ 代码

延伸阅读

  • 子集、组合、排列的区别在“什么时候收集”和“下一层从哪开始”:子集每个节点收集,组合叶子收集,排列用 used。
  • 去重的通用做法是先排序,再在同一层跳过相同元素。