
本文深入解析平衡括号生成算法的回溯执行机制,阐明为何在首次 res.push() 后递归仍能继续探索其他路径,并通过调用栈演化图与代码逻辑拆解,揭示 usedOpen > usedClose 这一关键剪枝条件如何保障路径完整性与合法性。
本文深入解析平衡括号生成算法的回溯执行机制,阐明为何在首次 `res.push()` 后递归仍能继续探索其他路径,并通过调用栈演化图与代码逻辑拆解,揭示 `usedopen > usedclose` 这一关键剪枝条件如何保障路径完整性与合法性。
平衡括号生成是回溯(Backtracking)与深度优先搜索(DFS)的经典范例。其核心目标是:给定正整数 n,生成所有由 n 对左右括号组成的、合法且互不重复的字符串。所谓“合法”,即任意前缀中左括号数量 ≥ 右括号数量,且最终左右括号总数均为 n。
关键在于理解:每一次 res.push(cur) 并不终止整个搜索过程,而仅结束当前递归分支的探索;函数会逐层返回至上一个决策点,尝试未执行的其他分支——这正是回溯算法“试探-失败-回退-再试探”的本质。
以 n = 2 为例,完整递归树如下(缩进表示调用深度):
Level 0: findCombo(2, "", 0, 0)
├─ Level 1: findCombo(2, "(", 1, 0) // 选 '('
│ ├─ Level 2: findCombo(2, "((", 2, 0) // 再选 '(' → usedOpen === n
│ │ └─ Level 3: findCombo(2, "(()", 2, 1) // 只能选 ')' → usedOpen > usedClose
│ │ └─ Level 4: findCombo(2, "(())", 2, 2) // 终止条件满足 → res.push("(())")
│ │ ← 返回 Level 3 → 无后续分支 → 返回 Level 2
│ │ ← 返回 Level 2 → 无其他分支(usedOpen === n,不能加'(';usedClose=0 usedClose(0) && usedClose usedClose → 无法加')')
└─ (Level 0 无其他分支)
可见,"(())" 和 "()()" 分别诞生于两个独立的递归子树,它们共享同一初始状态,但在 Level 1 就分叉:第一条路径优先填满左括号,第二条路径在满足约束前提下尽早引入右括号,从而拓展出不同结构。
算法中三个递归条件的作用需精确把握:
- if (usedOpen === n && usedClose === n):终止条件,收集当前解,但不 return —— 函数自然返回至上层;
- if (usedOpen 扩展左括号,无额外约束(只要没用完即可);
- if (usedOpen > usedClose && usedClose 扩展右括号的核心守门员。usedOpen > usedClose 确保添加 ')' 后仍保持前缀合法(不会出现 ")" 或 "())" 等非法前缀),usedClose
✅ 重要提醒:该函数依赖外部变量 res(如 let res = [])。生产环境中建议将其作为参数传入,避免隐式依赖和副作用:
function findCombo(n, cur, usedOpen, usedClose, res) { if (usedOpen === n && usedClose === n) { res.push(cur); return; // 显式 return 更清晰(虽非必需) } if (usedOpen usedClose && usedClose
总结来说,findCombo 的“持续迭代”并非线性循环,而是递归调用栈的自然展开与回退。每个 push 是某条 DFS 路径的终点,而栈的弹出使控制权交还给父节点,从而激活被暂时搁置的兄弟分支。理解这一栈式行为,是掌握所有回溯算法的关键基石。










