LC22 括号生成¶
中等通过率 79%python3核心代码模式回溯样例通过牛客 AC字符串动态规划回溯括号序列
讲解章节:DFS 深度优先搜索
一句话
逐个字符往下填,靠「左括号不超 n、右括号不超左括号」两条剪枝只走合法分支。
解题思路¶
这题考什么¶
暴力做法是枚举全部 2 的 2n 次方 个长度为 2n 的括号串再逐个验证, n = 8 时是 6.5e4 条串乘上验证开销,而合法的只有卡特兰数 C(8) = 1430 条, 绝大部分工作被浪费在必然不合法的分支上。
换成边填边判:一个括号串合法,等价于 (a) 左括号与右括号各恰好 n 个; (b) 任意前缀里右括号数不超过左括号数(否则某个右括号找不到配对的左括号)。 于是在第 k 个位置上只有两种选择,各配一条剪枝: 填 '(':要求已填左括号数 open < n,否则左括号会超额; 填 ')':要求 close < open,否则这个右括号立刻违反 (b)。 两条剪枝把搜索树砍成「只长出合法前缀」的形状,走到长度 2n 的叶子必定是一个合法解, 收答案时不需要再验证——合法性由不变量沿途保证。
n = 2 的搜索树(括号内是 open,close)
""(0,0) -> "("(1,0) -> "(("(2,0) -> "(()"(2,1) -> "(())"
-> "()"(1,1) -> "()("(2,1) -> "()()"
回溯的「选择 -> 递归 -> 撤销」三步见 DFS 深度优先搜索。
数据规模与复杂度¶
1 <= n <= 8。合法串的条数是第 n 个卡特兰数,n = 8 时为 1430。 剪枝后搜索树的叶子与答案一一对应,内部结点数是同一量级, 每条答案再花 O(n) 拼串,时间 O(n 乘 卡特兰数(n)),n = 8 时约 2e4 次基本操作。 空间 O(n):path 与递归栈都只有 2n = 16 层。
坑在哪¶
- 两条剪枝的条件不对称,别写成同一个模子。左括号看的是与上界 n 比(open < n), 右括号看的是与当前左括号数比(close < open)。 把后者误写成 close < n,会放出 "))((" 这类前缀非法的串。
- 出口用「已填长度达到 2n」判断即可,不必再检查 open == close == n: 两条剪枝保证了 open <= n 且 close <= open,长度到 2n 时只能是各 n 个。 多写一层判断不算错,但会让人误以为剪枝不完备。
- 收答案要 "".join(path) 生成新串。path 是全程复用的列表, 直接存 path 本身的话,回溯结束时它已被弹空,输出会变成一堆空列表。
- 每个 append 都要有配对的 pop,且必须写在递归之后。 漏掉任何一处撤销,兄弟分支就会带着上一条分支的残留字符继续往下填。
- 本题走严格比对,_judge.json 里没有给它配 unordered。 所以产出顺序必须与力扣官方一致。本文件的枚举顺序是「先试 '(' 再试 ')'」, 等价于把 '(' 看作比 ')' 小的字典序,恰好就是官方样例的排列顺序。 两个分支写反,n = 3 的第一条会变成 "()()()",整份输出都对不上。
样例复核¶
n = 3 按「先 '(' 后 ')'」产出:((()))、(()())、(())()、()(())、()()(), 共 5 条 = 卡特兰数 C(3),与样例逐条一致。
参考实现¶
[:octicons-arrow-left-16: LC21](LC21.md) [LC23 :octicons-arrow-right-16:](LC23.md)