LeetCode Hot100(40/100)——78. 子集

文章目录

    • 一、题目概述
    • 二、思维导图
    • 三、解法一:回溯法(Backtracking)
      • [1. 原理解析](#1. 原理解析)
      • [2. 递归流程图](#2. 递归流程图)
      • [3. Java 实现代码](#3. Java 实现代码)
      • [4. 时间与空间复杂度](#4. 时间与空间复杂度)
    • 四、解法二:迭代法(Iterative)
      • [1. 原理解析](#1. 原理解析)
      • [2. 流程图](#2. 流程图)
      • [3. Java 实现代码](#3. Java 实现代码)
      • [4. 时间与空间复杂度](#4. 时间与空间复杂度)
    • [五、解法三:位运算法(Bit Manipulation)](#五、解法三:位运算法(Bit Manipulation))
      • [1. 原理解析](#1. 原理解析)
      • [2. 位运算流程图](#2. 位运算流程图)
      • [3. Java 实现代码](#3. Java 实现代码)
      • [4. 时间与空间复杂度](#4. 时间与空间复杂度)
    • 六、综合对比
    • 七、总结

一、题目概述

题目描述:

给定一个不含重复元素的数组 nums,返回该数组所有可能的子集(幂集)。

示例:

复制代码
输入:nums = [1,2,3]
输出:[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]

说明:

  • 子集的数量为 2^n(n 为数组长度)。
  • 子集顺序不重要。

二、思维导图

子集问题 Subsets
基本思路

  • 枚举所有元素组合 - 每个元素有"选"或"不选"两种状态 解法类别
    回溯法
    迭代法
    位运算枚举法
    分析维度
    时间复杂度
    空间复杂度
    可扩展性

三、解法一:回溯法(Backtracking)

1. 原理解析

思想核心:

我们尝试对数组中的每个元素进行「选」或「不选」,通过回溯递归逐层构建所有可能的子集。

每进入一层递归,都表示我们正在考虑数组中的第 i 个元素是否放入当前子集。

2. 递归流程图



开始
调用 backtrack(\[\], 0)
idx == nums.length?
加入当前子集到结果集
不选择 numsidx
backtrack(current, idx+1)
选择 numsidx
backtrack(current+nums\[idx], idx+1)
返回上一层

3. Java 实现代码

java 复制代码
import java.util.*;

public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        backtrack(res, new ArrayList<>(), nums, 0);
        return res;
    }

    private void backtrack(List<List<Integer>> res, List<Integer> temp, int[] nums, int start) {
        res.add(new ArrayList<>(temp));
        for (int i = start; i < nums.length; i++) {
            temp.add(nums[i]);
            backtrack(res, temp, nums, i + 1);
            temp.remove(temp.size() - 1);
        }
    }
}

4. 时间与空间复杂度

  • 时间复杂度: O(2^n)
    每个元素可选或不选,共 2^n 种组合。
  • 空间复杂度: O(n)(递归栈深度 + 临时子集存储)

四、解法二:迭代法(Iterative)

1. 原理解析

每加入一个新元素 x,都在当前已有子集中附加一份"包含该元素"的新子集。

例如:

复制代码
nums = [1,2,3]

初始: [[]]
加入 1: [[],[1]]
加入 2: [[],[1],[2],[1,2]]
加入 3: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

2. 流程图

初始结果 \[]
遍历 nums
对当前结果集 res 中每个 subset 添加 numsi
更新结果集 res
完成所有遍历 → 返回 res

3. Java 实现代码

java 复制代码
import java.util.*;

public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        res.add(new ArrayList<>());
        for (int num : nums) {
            List<List<Integer>> newSubsets = new ArrayList<>();
            for (List<Integer> subset : res) {
                List<Integer> temp = new ArrayList<>(subset);
                temp.add(num);
                newSubsets.add(temp);
            }
            res.addAll(newSubsets);
        }
        return res;
    }
}

4. 时间与空间复杂度

  • 时间复杂度: O(2^n * n)
    每次复制已有子集列表。
  • 空间复杂度: O(2^n * n)(存储全部子集)

五、解法三:位运算法(Bit Manipulation)

1. 原理解析

对每个整数从 02^n - 1,用其二进制位来表示「选中状态」:

  • 二进制第 i 位为 1 表示选中 nums[i]
  • 为 0 表示未选。

例如:
nums = [1, 2, 3]

复制代码
000 -> []
001 -> [3]
010 -> [2]
011 -> [2,3]
100 -> [1]
101 -> [1,3]
110 -> [1,2]
111 -> [1,2,3]

2. 位运算流程图

for mask in (0 .. 2^n-1)
初始化空子集 subset
检查每一位 bit 是否为 1
若为1则加入对应 numsi
将 subset 加入结果集 res

3. Java 实现代码

java 复制代码
import java.util.*;

public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        int n = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        for (int mask = 0; mask < (1 << n); mask++) {
            List<Integer> subset = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                if ((mask & (1 << i)) != 0) {
                    subset.add(nums[i]);
                }
            }
            res.add(subset);
        }
        return res;
    }
}

4. 时间与空间复杂度

  • 时间复杂度: O(2^n * n)
  • 空间复杂度: O(2^n * n)

六、综合对比

解法 思路特点 时间复杂度 空间复杂度 适用场景
回溯法 递归建树结构,自然 O(2^n) O(n) 理解递归思路练习
迭代法 动态生成新子集 O(2^n * n) O(2^n * n) 实现简单,非递归
位运算法 利用二进制状态枚举 O(2^n * n) O(2^n * n) 高效、直观实现

七、总结

  • 子集问题是典型的 组合枚举问题,关键在于理解「选与不选」的二元决策。
  • 回溯法最直观;迭代法实现简洁;位运算法最具数学美感。
  • 掌握这三种思路,将有助于解决其他与「组合 / 幂集 / 枚举」相关的问题,如:
    • 子集和问题(Subset Sum)
    • 子序列生成
    • 组合总和(Combination Sum)
相关推荐
圣保罗的大教堂2 分钟前
leetcode 3016. 输入单词需要的最少按键次数 II 中等
leetcode
hhzz8 分钟前
机器学习-算法模型系列文章:07-SVM 一巴掌拍出来的超平面:SVM核函数选错,再干净的数据也救不了你
算法·机器学习·支持向量机
geovindu20 分钟前
java: Gale-Shapley Algorithm
java·开发语言·后端·算法
冻柠檬飞冰走茶40 分钟前
PTA基础编程题目集 7-34 通讯录的录入与显示(C语言实现)
c语言·开发语言·数据结构·算法
zander25844 分钟前
LeetCode 79. 单词搜索
算法·深度优先
老洋葱Mr_Onion1 小时前
【C++】高精度模板
开发语言·c++·算法
脚踏实地皮皮晨1 小时前
003003002_WPF Grid 基类官方类定义逐行深度解析
开发语言·windows·算法·c#·wpf·visual studio
aaaameliaaa10 小时前
字符函数和字符串函数
c语言·笔记·算法
城管不管11 小时前
ReAct、Plan-and-Execute、Reflection 三大智能 Agent 范式核心区别
java·人工智能·算法·spring·ai·动态规划
月疯11 小时前
二分法算法(水平等分图形面积)
算法