数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入: n = 3
输出: ["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入: n = 1
输出: ["()"]
提示:
1 <= n <= 8
1. 生活案例:搭建平衡木桥
想象你在玩一个搭建平衡木桥的游戏:
-
材料:你有 块左木板
(和 块右木板)。 -
规则:
- 左木板先行:你手里必须先有左木板,才能往右边搭。
- 保持平衡:在任何时候,你已经搭上去的右木板数量,都不能超过左木板的数量(否则桥会塌,即括号无效)。
- 用完为止:当 块木板全部搭完,且符合上述规则,你就建成了一座合法的桥。
2. 代码实现与详细注释
这是你图片中的代码,我为你添加了详细的“筑墙规则”注释:
JavaScript
/**
* @param {number} n - 括号的对数
* @return {string[]} - 所有有效的组合
*/
var generateParenthesis = function(n) {
let res = []; // 【仓库】:存放所有盖好的合格建筑
/**
* @param {string} str - 当前已经搭好的部分
* @param {number} left - 已经用了多少个左括号
* @param {number} right - 已经用了多少个右括号
*/
function backtrack(str, left, right) {
// 【终点】:如果长度达到了 2n,说明木板全用完了
if (str.length === 2 * n) {
res.push(str); // 竣工,存入仓库
return;
}
// 【规则1】:只要左括号还没用完(小于 n),就可以继续放左括号
if (left < n) {
backtrack(str + '(', left + 1, right);
}
// 【规则2】:只有当“已经放下的右括号”比“左括号”少时,才能放右括号
// 这保证了每一个 ')' 之前一定有一个 '(' 对应,不会出现 ")(" 这种无效情况
if (right < left) {
backtrack(str + ')', left, right + 1);
}
}
// 从空地、0个左括号、0个右括号开始搭
backtrack('', 0, 0);
return res;
};
3. 核心原理解析
为什么这个逻辑能过滤掉无效括号?
回溯算法在这里其实是在走一棵“决策树”。传统的暴力法会生成 种组合然后再判断是否有效,而这个算法在生长的过程中就剪掉了错枝:
- 比如 ,当路径变成
())时,由于right(2) > left(1),这个分支在代码逻辑里根本不会被触发。 - 这大大减少了计算量,确保生出来的每一个成品都是合格的。
运行轨迹追踪(以 为例)
- 开始:
""(0,0) - 放左:
"((1,0) - 放左:
"(("(2,0) 此时只能放右:"(()"(2,1) 再放右:"(())"(2,2) 【完成1】 - 回到第2步,改放右:
"()"(1,1) 此时只能放左:"()("(2,1) 再放右:"()()"(2,2) 【完成2】
复杂度分析
- 时间复杂度:这是一个著名的**卡特兰数(Catalan number)**问题。复杂度约为 。
- 空间复杂度:,主要是递归搜索树的深度。