题目描述
思路解析动画文字版
思路一句话:从 start 往后选(升序去重),凑够 k 个就收集,剩数不够补满就剪枝。下面一步步演给你看。
选数字 1(下标 0)。还需选 1 个,下一层只能从它右边(下标 1)继续,这样组合天然升序、不会重复。
选数字 2(下标 1)。还需选 0 个,下一层只能从它右边(下标 2)继续,这样组合天然升序、不会重复。
path 已凑满 2 个 → 收集组合 [1, 2]。这是第 1 个答案。
回退:撤销 2,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
选数字 3(下标 2)。还需选 0 个,下一层只能从它右边(下标 3)继续,这样组合天然升序、不会重复。
path 已凑满 2 个 → 收集组合 [1, 3]。这是第 2 个答案。
回退:撤销 3,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
选数字 4(下标 3)。还需选 0 个,下一层只能从它右边(下标 4)继续,这样组合天然升序、不会重复。
path 已凑满 2 个 → 收集组合 [1, 4]。这是第 3 个答案。
回退:撤销 4,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
回退:撤销 1,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
选数字 2(下标 1)。还需选 1 个,下一层只能从它右边(下标 2)继续,这样组合天然升序、不会重复。
选数字 3(下标 2)。还需选 0 个,下一层只能从它右边(下标 3)继续,这样组合天然升序、不会重复。
path 已凑满 2 个 → 收集组合 [2, 3]。这是第 4 个答案。
回退:撤销 3,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
选数字 4(下标 3)。还需选 0 个,下一层只能从它右边(下标 4)继续,这样组合天然升序、不会重复。
path 已凑满 2 个 → 收集组合 [2, 4]。这是第 5 个答案。
回退:撤销 4,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
回退:撤销 2,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
选数字 3(下标 2)。还需选 1 个,下一层只能从它右边(下标 3)继续,这样组合天然升序、不会重复。
选数字 4(下标 3)。还需选 0 个,下一层只能从它右边(下标 4)继续,这样组合天然升序、不会重复。
path 已凑满 2 个 → 收集组合 [3, 4]。这是第 6 个答案。
回退:撤销 4,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
回退:撤销 3,把它从 path 拿掉,换右边的数再试(这就是「回溯」——试完一条路就退回来换下一条)。
剪枝 ✗:还需 2 个,但从下标 3 往后只剩 1 个,凑不满了,直接砍掉这一支,省去无用搜索。
搜索结束:共找到 6 个组合。回溯就是「选一个→往深搜→退回来换下一个」,配合 start 升序和剪枝,不重不漏。
边界先想清。
两个高频追问。
参考代码
class Solution: def combine(self, n, k): res, path = [], [] def bt(start): if len(path) == k: res.append(path[:]); return # 剪枝:剩余不够补满 k 个就停 for i in range(start, n - (k - len(path)) + 2): path.append(i) bt(i + 1) path.pop() bt(1) return res复杂度
- 时间:O(C(n,k)·k),共 C(n,k) 个组合,每个拷贝 k 个数
- 空间:O(k),递归深度 + path 长度都是 k
易错点
面试追问把动画讲成自己的话
追问组合和排列回溯的区别?
追问剪枝的上界怎么推的?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
子集
LeetCode 78 · 中等 · 沿着 回溯套路 继续往下推进