图解题库 / 回溯 · 括号

22. 括号生成

中等 含交互动画

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

题目描述

给 n 对括号,生成所有合法的括号组合(每个左括号都能正确配对)。

n = 3
输出 = ((())) (()()) (())() ()(()) ()()()

思路解析

把所有 ( 和 ) 的排列都生成出来再挑合法的,绝大多数都是废串,白生成。

回溯的精髓——构建过程中只要发现不可能合法,立刻掐断这条分支,连试都不试。

两条规则:左括号还有剩就能放 (;已放的右括号比左括号少,才能放 )。守住这两条,生成的一定合法。

放第 1 个:(:开局只能放 ((一个 ) 都没法配对)。路径变成 "(",左括号还剩 2 个。

连放左括号:一路把 3 个左括号都放完,路径 "((("。左括号用光了,接下来只能放右括号。

补齐右括号:把 3 个右括号补上,得到 "((()))"。左右配平、长度够了,收进结果

回溯,换分支:收完一条就往回退(撤销刚放的括号),回到还能拐弯的地方,改放 ) 试另一种走法。

所有分支走完:这样不断「放-收-回退-换」,把每条合法路径都走一遍,最终凑齐全部 5 种

回溯类题的通用思路:把「什么时候合法」翻译成「什么时候能往下放」,不合法的分支直接不进,效率天差地别。

参考代码(Python)

Python
1def generateParenthesis(n):
2    res = []
3    def bt(path, left, right):        # left/right = 还能放几个
4        if len(path) == 2 * n:
5            res.append(class="cl-str">"".join(path)); return
6        if left > 0:                  # 左括号有剩,能放 (
7            bt(path + [class="cl-str">"("], left - 1, right)
8        if right > left:              # 右比左多得剩,能放 )
9            bt(path + [class="cl-str">")"], left, right - 1)
10    bt([], n, n)
11    return res

复杂度分析

  • 时间复杂度:第 n 个卡特兰数 —— 约 4ⁿ / (n√n)
  • 空间复杂度:O(n) —— 递归深度 2n

套路模板

所有回溯都是这个骨架:到终点就收、否则枚举合法选择、选了再撤。本题用「left/right 计数」充当那个「状态」。

Python
1def bt(path, 状态):
2    if 到达终点:
3        res.append(path 的快照); return
4    for 每个合法选择:
5        做选择(更新 path 和状态)
6        bt(path, 新状态)
7        撤销选择(回溯)

易错点

  • 放 ) 的条件写成 right > 0 → ✅ right > left(右括号必须比左括号「少」才能再放 ),否则会出现 )( 这种配不上对的串)
  • 收结果时直接存 path 引用 → ✅ 存 "".join(path) 或 path[:] 的快照(path 后面还会被修改,存引用会让结果全变成最后那条)
  • 用 path + ["("] 之外还手动回溯 → ✅ 二选一:要么传新列表、要么 append 后 pop(混用会重复撤销,路径乱套)

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

下一题 →77. 组合 ← 返回题库