77. 组合

中等 含交互动画

学完这道你应该能: 说清它为什么用「组合」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

给两个整数 n 和 k,返回 1..n 中所有 k 个数的组合(组合不讲顺序,[1,2] 和 [2,1] 算同一个)。

n = 4
k = 2
输出 = [1,2][1,3][1,4][2,3][2,4][3,4]

思路解析

k=2 写两层 for,k=5 写五层……k 是变量时,嵌套循环根本写不出来。

转折:用递归代替「不定层循环」。每选一个数就从它后面继续选(start 保证只往后、天然去重,[1,2] 不会再冒出 [2,1]);选够 k 个就收进结果,然后撤销刚才那一步(path.pop)回到上层,换下一个候选。选→递归→撤销,三步循环。

选 1 · path=[1]:先选 1,path=[1]。接下来 start=2,只能从 2 往后挑,1 不再回头看。还没够 k=2 个,继续往下选。

选 2 · 凑够 2 个 → 收:再选 2,path=[1,2],长度到 k=2,收进结果 [1,2]。这条分支到头了,该回退。

撤销 2 · path=[1](回溯):负例/回溯动作:把刚选的 2 弹出,path 退回 [1],2 重新变回「可选」。不撤销的话 path 会越积越长、污染后面的分支。回到上层接着试下一个候选。

选 3 · 收 [1,3]:在 [1] 基础上改选 3,path=[1,3],够 2 个,收下 [1,3]。再撤销 3、改选 4……

选 4 · 收 [1,4]:同理收下 [1,4]。1 后面 2、3、4 都配过了,1 打头的组合全齐。接着把 1 也撤销,换头。

换头 · 2 打头 → [2,3]:回到最外层,改用 2 打头(start=3),收 [2,3]。注意 1 已撤销、不在 path 里——start 保证不会再回头配到 1。

2 打头 · 再收 [2,4]:撤销 3、改选 4,收 [2,4]。2 打头的也配完了,再撤销 2 换成 3 打头。

3 打头 · 收齐全部 6 个:3 打头收下 [3,4]。(4 打头后面没数了,凑不齐 2 个,是条死分支,直接返回。)最终 6 个组合全部收齐

组合类题的核心——用 start 下标避免重复,让不定层数的枚举变成一个干净的递归。

参考代码(Python)

Python
1def combine(n, k):
2    res = []
3    def bt(start, path):
4        if len(path) == k:                # 够 k 个,收
5            res.append(path[:]); return
6        for i in range(start, n + 1):     # 从 start 往后挑
7            path.append(i)
8            bt(i + 1, path)               # 下一层从 i+1 开始
9            path.pop()                    # 撤销,回溯
10    bt(1, [])
11    return res

复杂度分析

  • 时间复杂度:O(C(n,k) · k) —— 收每个组合要 O(k)
  • 空间复杂度:O(k) —— 递归深度 k

套路模板

骨架记牢:for 从 start 起、递归传 i+1(不是 start+1)、选完 pop。这是所有「组合 / 子集」题的母模板。

Python
1def bt(start, path):
2    if 满足收集条件:
3        res.append(path[:]); return
4    for i in range(start, n + 1):
5        path.append(i)
6        bt(i + 1, path)        # 关键:i+1
7        path.pop()

易错点

  • 错误写法:下一层传 start + 1正确写法:传 i + 1(要从「当前选的 i」后面继续,不是从 start 后面,传 start+1 会重复选)
  • 错误写法:res.append(path)正确写法:res.append(path[:])(path 是同一个列表,会被后续 pop 改掉,必须存一份拷贝)
  • 错误写法:选完忘了 path.pop()正确写法:递归后立刻 pop 撤销(不撤销,path 会越积越长,把别的分支也污染了)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →17. 电话号码的字母组合 ← 返回题库