题目描述

✅ 面试题 08.04. 幂集

image-20260929012100427

题意分析

返回元素互不相同的整数集合的全部子集,包括空集。每个元素在一个子集中只可能出现一次,子集及其元素的输出顺序不影响答案。

子集只关心哪些元素被选中,不需要重新安排元素位置。因此可以固定输入顺序,对每个元素分别决定“选”或“不选”,避免把同一组元素的不同排列重复输出。

解法:按元素做选与不选的回溯

核心思路

[!blue]

定义 dfs(u, t):下标 [0, u) 的元素已经决定是否选取,t 保存其中选中的元素;从 u 开始的元素还未处理。

当前元素只有两个互斥选择。不选时,路径不变,递归到 u + 1;选择时,先把 nums[u] 加到路径末尾,再递归到 u + 1,返回后恢复本层路径。两种选择都只推进下标,不会再次处理已经决定过的元素。

当 u == nums.length,所有元素都已决定,当前路径就是一个完整子集。每条从根到叶的路径对应一个唯一的选取方案;反过来,任意子集也唯一确定每个元素选还是不选。输入元素互异,因此这些叶子既不重复,也不遗漏。

保存答案时必须复制当前路径。Java 的列表是可变对象,Go 的切片也可能共用底层数组,直接保留当前容器会让后续搜索影响已经保存的子集。复制只发生在叶子,搜索过程中继续复用路径。

解题步骤

  1. 从下标 0 和空路径开始搜索。
  2. 如果下标已到数组末尾,复制当前路径加入答案,并结束当前调用。
  3. 先递归处理“不选当前元素”的分支。
  4. 将当前元素追加到路径,递归处理“选择当前元素”的分支,再恢复路径长度。
  5. 全部递归结束后返回答案。一路不选会得到空集;输入为空时,初次调用就收集空路径,结果仍包含一个空集。

代码实现

// u 之前的元素已决定,t 保存其中被选中的元素。
class Solution {
    private List<List<Integer>> answer = new ArrayList<>();
    private int[] nums;

    public List<List<Integer>> subsets(int[] nums) {
        this.nums = nums;
        dfs(0, new ArrayList<>());

        return answer;
    }

    private void dfs(int u, List<Integer> t) {
        if (u == nums.length) {
            answer.add(new ArrayList<>(t));

            return;
        }

        dfs(u + 1, t);
        t.add(nums[u]);
        dfs(u + 1, t);
        t.remove(t.size() - 1);
    }
}
// u 之前的元素已决定,t 保存其中被选中的元素。
func subsets(nums []int) [][]int {
    var answer [][]int
    var dfs func(u int, t []int)
    dfs = func(u int, t []int) {
        if u == len(nums) {
            answer = append(answer, append([]int(nil), t...))
            return
        }
        dfs(u+1, t)
        t = append(t, nums[u])
        dfs(u+1, t)
        t = t[:len(t)-1]
    }
    var t []int
    dfs(0, t)
    return answer
}

复杂度分析

  • 时间复杂度:$O(n \cdot 2^n)$。共有 $2^n$ 个子集,复制一个子集最坏需要 $O(n)$;所有输出元素总数实际为 $n \cdot 2^{n-1}$。
  • 空间复杂度:不计返回结果时为 $O(n)$,来自递归栈与当前路径;结果本身占 $O(n \cdot 2^n)$。

关键点总结

[!green]

  • 下标 u 表示已决定的元素数量,路径 t 表示这些元素中被选中的部分。
  • 每个元素的选与不选构成完整且互斥的划分,不需要额外的去重集合。
  • 本实现只在所有元素决定完后收集结果,空集也会自然经过这个终止条件。

易错点总结

[!yellow]

  • 叶子处直接保存路径引用,后续修改可能覆盖已保存的结果,应复制元素。
  • 共享可变路径完成选择分支后要恢复,避免影响其他分支的状态。
  • 空输入的幂集仍包含空集,不能直接当作没有任何子集。
  • 时间开销不仅来自访问决策树,还包括复制并输出所有子集的元素。

相似题目

题目 难度 关联与区别
90. 子集 II 中等 输入含重复值时要处理相同元素选择造成的重复子集,本题元素互异。
46. 全排列 中等 同样回溯枚举,本题每个元素只决定选不选,排列题还要决定位置顺序。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/94530230
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!