题目描述
思路解析动画文字版
子集数量本来就是 2ⁿ,躲不开。关键是咋系统地枚举,既不漏掉,也不会出现 [1,2] 和 [2,1] 这种重复。
从空集出发,每走到一个节点就把当前 path 记为一个子集;然后只从「上次选的位置之后」继续选下一个数(start 索引),这样保证每个子集元素递增、绝不重复。选完撤销,回头试别的。
起点 · 记录空集:一开始 path 空,先把空集 [] 记进结果——它也是一个合法子集。
选 1:选 1,path=[1],立刻记录。接下来只能从 1 后面(2、3)里选。
再选 2:在 [1] 基础上选 2,path=[1,2],记录。继续只能往后选 3。
再选 3:再选 3,path=[1,2,3],记录。后面没数可选了,这条路走到底,开始回溯。
撤销回到 [1] · 选 3:撤销 3、撤销 2,回到 [1]。这次跳过 2 直接选 3,得到 [1,3] 并记录。
回到根 · 选 2:彻底撤回到空,从 2 开头:path=[2] 记录。注意不会再选 1(start 在 2 后面),所以不会出现 [2,1]。
选 2 后选 3:在 [2] 后选 3,得 [2,3] 记录。
最后 · 选 3:回到根,从 3 开头:[3]。8 个子集全部集齐,结束。
回溯就是在决策树上 DFS:进入一个分支前「做选择」、退出时「撤销选择」。子集、组合、排列、分割都是它。
迁移阶梯:把这题练到能复述后,再去同类题里迁移:先复用状态定义,再调整边界条件。
边界三连:边界三连:极端输入先过一遍。
几个高频追问,记住答法。
参考代码
class Solution: def subsets(self, nums): ans = [] def backtrack(start, path): ans.append(path[:]) for i in range(start, len(nums)): path.append(nums[i]) backtrack(i + 1, path) path.pop() backtrack(0, []) return ans复杂度
- 时间复杂度:O(n · 2ⁿ),2ⁿ 个子集,每个复制 O(n)
- 空间复杂度:O(n),递归深度 + path
可套用的代码模板
记住骨架:选→递归→撤销。组合/子集用 start 去重;排列用 used 数组;允许复用就递归传 i 而非 i+1。
def backtrack(start, path): if 满足结束条件: res.append(path[:]); return # 或每节点都收集 for i in range(start, n): path.append(choices[i]) # ① 选 backtrack(i + 1, path) # ② 递归(i+1去重/i可复用) path.pop() # ③ 撤销易错点
面试追问把动画讲成自己的话
追问回溯三件套是什么?
追问子集 / 组合 / 排列的区别?
追问有重复元素(LC90)怎么去重?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
组合总和
LeetCode 39 · 中等 · 沿着 回溯 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题