news 2026/5/28 20:40:33

【力扣100题】69.子集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【力扣100题】69.子集

题目描述

给你一个整数数组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 中的所有元素互不相同

解题思路总览

方法核心思想时间复杂度空间复杂度备注
回溯(DFS)深度优先搜索枚举所有子集O(2^n * n)O(n)推荐解法
迭代(位运算)用二进制位表示子集O(2^n * n)O(1)经典方法
递归数学归纳思想O(2^n * n)O(n)另一种思路

一、核心解法:回溯(DFS)

核心思想

回溯算法:从空集开始,逐步添加元素,枚举所有可能的子集。

关键洞察: 1. 子集问题的本质: - 每个元素都有两个选择:选或不选 - 共 2^n 种子集 - 与排列不同,子集不关心元素的顺序 2. 状态表示: - path: 当前已选择的元素(子集) - index: 当前处理到第几个元素 - 从 index 开始选择后面的元素 3. 回溯思想: - 每个位置有两种选择:选 nums[i] 或不选 - 通过控制 index 实现"选或不选"的选择

图解

nums = [1, 2, 3] 决策树: [] / | \ [] [1] [2] [3] / | / | / | / | [] [1] [1] [1,2] [2] [2,3] [3] [1,3] ... ... ... ... ... ... ... ... 完整决策树: 从 [] 开始: index=0: 选择或不选择 1 ├── 不选 1: index=1 │ ├── 不选 2: index=2 │ │ ├── 不选 3: 输出 [] │ │ └── 选 3: 输出 [3] │ └── 选 2: index=2 │ ├── 不选 3: 输出 [2] │ └── 选 3: 输出 [2,3] └── 选 1: index=1 ├── 不选 2: index=2 │ ├── 不选 3: 输出 [1] │ └── 选 3: 输出 [1,3] └── 选 2: index=2 ├── 不选 3: 输出 [1,2] └── 选 3: 输出 [1,2,3] 所有子集: [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]

二、算法流程图

输入: nums = [1, 2, 3] 初始化: ans = [] path = [] 递归 DFS(index=0): ans.push_back([]) // 空集 遍历 i = 0, 1, 2: i=0: path.push_back(1) -> path=[1] DFS(index=1) ans.push_back([1]) 遍历 i=1,2: i=1: path.push_back(2) -> path=[1,2] DFS(index=2) ans.push_back([1,2]) 遍历 i=2: i=2: path.push_back(3) -> path=[1,2,3] DFS(index=3) ans.push_back([1,2,3]) path.pop_back() -> path=[1,2] 回溯 回溯 path.pop_back() -> path=[1] 回溯 回溯 path.pop_back() -> path=[] 回溯 ... 继续 i=1, i=2 输出: [[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]]

三、完整代码实现

classSolution{public:vector<vector<int>>subsets(vector<int>&nums){vector<vector<int>>ans;vector<int>path;// 回溯函数function<void(int)>dfs=[&](thisauto&&self,intindex){// 每一层都记录当前的子集ans.push_back(path);// 从 index 开始,尝试选择或不选择每个元素for(inti=index;i<nums.size();i++){// 选择 nums[i]path.push_back(nums[i]);// 递归处理下一个元素(从 i+1 开始)self(i+1);// 回溯:撤销选择path.pop_back();}};// 开始回溯,从索引 0 开始dfs(0);returnans;}};

四、逐行解析

vector<vector<int>>ans;vector<int>path;
  • ans:存储所有子集
  • path:当前正在构建的子集
ans.push_back(path);
  • 每一层递归都将当前 path(子集)加入 ans
  • 这保证了空集和所有中间子集都被记录
for(inti=index;i<nums.size();i++){
  • 从 index 开始遍历
  • index控制我们只能选择当前及之后的元素
  • 这样可以避免重复(比如 [1,2] 和 [2,1])
path.push_back(nums[i]);self(i+1);path.pop_back();
  • 选择 nums[i],递归处理下一个
  • 回溯时撤销选择,尝试其他可能

五、与全排列的对比

维度第 78 题 子集第 46 题 全排列
问题本质每个元素选或不选排列所有元素
结果数量2^n 种子集n! 种排列
选择方式线性选择(index 控制)环形选择(used 数组)
去重需求不需要(元素互不相同)不需要
时间复杂度O(2^n * n)O(n! * n)
子集 vs 排列: 子集 [1,2,3]: - 不关心顺序,{1,2} 和 {2,1} 是同一个子集 - 每个元素选或不选,2^n 种 排列 [1,2,3]: - 关心顺序,{1,2} 和 {2,1} 是不同排列 - 每个位置选一个未使用的元素,n! 种

六、位运算解法

位运算思想:用二进制位表示子集 nums = [1, 2, 3] 二进制表示: 000 -> [] (都不选) 001 -> [3] (选第3个) 010 -> [2] (选第2个) 011 -> [2,3] (选第2、3个) 100 -> [1] (选第1个) 101 -> [1,3] (选第1、3个) 110 -> [1,2] (选第1、2个) 111 -> [1,2,3] (选全部) 代码实现: for (int mask = 0; mask < (1 << n); mask++) { vector<int> path; for (int i = 0; i < n; i++) { if (mask & (1 << i)) { path.push_back(nums[i]); } } ans.push_back(path); }

七、复杂度分析

方法时间复杂度空间复杂度备注
回溯O(2^n * n)O(n)推荐
位运算O(2^n * n)O(1)经典方法
递归O(2^n * n)O(n)数学归纳

详细分析:

时间复杂度: 共 2^n 种子集 每种子集需要 O(n) 复制到 ans 总计:O(2^n * n) 空间复杂度: 递归深度:O(n) path:O(n) 总计:O(n)

八、边界情况分析

情况处理方式
n = 0返回 [[]](空集的子集只有空集本身)
n = 1返回 [[], [nums[0]]]
全部正数正常处理
有负数正常处理,负数不影响子集生成

示例:n = 1

nums = [5] 回溯过程: path = [] DFS(0): ans.push_back([]) // 空集 遍历 i=0: path.push_back(5) -> path=[5] DFS(1): ans.push_back([5]) // 只有5的子集 path.pop_back() -> path=[] 输出: [[], [5]]

九、面试追问 FAQ

问题回答要点
Q: 子集问题的核心是什么?每个元素都有选或不选两种情况,共 2^n 种
Q: 为什么每个递归都要 push path?因为 path 的每个状态都是一个子集,包括空集
Q: 为什么 index 是 i+1 而不是 index+1?保证不回头选择已经跳过的元素,避免重复子集
Q: 时间复杂度为什么是 O(2^n * n)?2^n 种子集,每种需要 O(n) 复制
Q: 空间复杂度是多少?O(n)(递归深度 + path)
Q: 如何处理有重复数字的子集?需要排序 + 跳过相同元素(参考第 90 题)

十、相关题目

题目编号题目名称难度核心差异
78子集中等基础题,无重复数字
90子集 II中等有重复数字,需要去重
46全排列中等排列问题
77组合中等组合问题
131分割回文串中等子集变形
416分割等和子集困难子集 + 背包

十一、总结

要点内容
核心思想回溯/位运算,枚举每个元素选或不选
关键点每一层都记录当前子集到 ans
遍历方式从 index 开始,避免重复子集
时间复杂度O(2^n * n)
空间复杂度O(n)
变形位运算:用二进制位表示子集

子集问题是经典的回溯/枚举问题,核心思想是每个元素都有选或不选两种情况,共 2^n 种子集。通过回溯或位运算可以高效枚举所有子集。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 20:39:22

Unity几何着色器画虚线实战:从原理到代码,打造高性能动态路径线

Unity几何着色器画虚线实战&#xff1a;从原理到代码打造高性能动态路径线在游戏开发中&#xff0c;动态路径线的实现一直是视觉效果与性能平衡的难题。无论是角色移动预测、技能弹道轨迹还是实时导航指示&#xff0c;传统的LineRenderer或片元着色器方案往往难以兼顾灵活性与效…

作者头像 李华
网站建设 2026/5/28 20:37:19

解密PixelSmile核心技术:Qwen模型如何实现像素级表情操控

解密PixelSmile核心技术&#xff1a;Qwen模型如何实现像素级表情操控 【免费下载链接】PixelSmile 项目地址: https://ai.gitcode.com/hf_mirrors/PixelSmile/PixelSmile PixelSmile是一款基于Qwen-Image-Edit-2511模型开发的细粒度面部表情编辑工具&#xff0c;它通过…

作者头像 李华
网站建设 2026/5/28 20:35:33

JetBrains IDE 试用期重置插件:深度解析与实践指南

JetBrains IDE 试用期重置插件&#xff1a;深度解析与实践指南 【免费下载链接】ide-eval-resetter 项目地址: https://gitcode.com/gh_mirrors/id/ide-eval-resetter JetBrains IDE 试用期重置工具是开发者解决评估期限制问题的专业解决方案。通过系统性地清理评估文件…

作者头像 李华