生成括号算法js

admin 102 0
括号匹配算法是JavaScript中处理字符串括号有效性的常用方法,核心基于栈结构,实现时,初始化空栈遍历字符串:遇左括号(如(、[、{)则入栈,遇右括号则检查栈顶是否匹配对应左括号且栈非空,匹配则出栈,否则直接返回false,遍历结束后,若栈为空则所有括号有效,否则无效,典型应用如校验代码、表单输入格式等,用数组模拟栈,通过push/pop操作高效完成判断,时间复杂度O(n),空间复杂度O(n)。

JavaScript实现生成括号算法:递归、迭代与动态规划

在算法学习中,"生成有效括号组合"是一个经典问题:给定整数 n,生成所有由 n 对括号组成的、且格式正确的组合,当 n=2 时,输出为 ["(())","()()"],这个问题不仅考察对括号匹配规则的理解,更涉及递归、迭代、动态规划等多种算法思想,本文将详细介绍如何用 JavaScript 实现生成括号算法,从基础递归到高效动态规划,逐步优化解决方案。

问题理解与核心思路

有效括号的规则

一个括号组合有效,需满足以下两个条件:

  1. 左括号数量等于右括号数量(n 对);
  2. 在任意前缀中,左括号数量不少于右括号数量(避免 这样的非法组合)。

核心思路

生成有效括号的本质是"按规则添加括号":

  1. 从空字符串开始,每次选择添加 或 ;
  2. 添加时需确保左括号数量不超过 n,且右括号数量不超过左括号数量;
  3. 当字符串长度达到 2n 时,得到一个有效组合。

递归回溯法(最直观)

递归是解决此类"组合生成"问题的天然选择,通过递归枚举所有可能的括号添加路径,并在非法路径时提前终止(剪枝),即可高效生成有效组合。

算法步骤

  1. 定义递归函数,参数包括当前字符串 current、左括号数量 left、右括号数量 right
  2. 终止条件:当 current.length === 2n 时,将当前组合加入结果数组;
  3. 递归过程:
    • left < n,可添加 ,递归调用 left + 1
    • right < left,可添加 ,递归调用 right + 1

JavaScript 实现

function generateParenthesis(n) {
    const result = [];
    function backtrack(current, left, right) {
        // 终止条件:字符串长度达到 2n
        if (current.length === 2 * n) {
            result.push(current);
            return;
        }
        // 添加左括号(若左括号未用完)
        if (left < n) {
            backtrack(current + '(', left + 1, right);
        }
        // 添加右括号(若右括号少于左括号)
        if (right < left) {
            backtrack(current + ')', left, right + 1);
        }
    }
    backtrack('', 0, 0);
    return result;
}
// 示例:n=2
console.log(generateParenthesis(2)); 
// 输出:["(())", "()()"]

复杂度分析

  • 时间复杂度O(4^n / sqrt(n)),第 n 个卡特兰数(有效括号组合数)约为 4^n / (n * sqrt(n)),每个组合需要 O(n) 时间构建,故总时间复杂度为指数级。
  • 空间复杂度O(n)(递归调用栈的最大深度为 2n,但卡特兰数的影响已包含在时间复杂度中)。

实际应用场景

递归回溯法虽然时间复杂度较高,但在 n 较小(如 n ≤ 8)时实现简单直观,适合教学演示和快速原型开发,在实际面试中,这种方法往往能清晰展示对问题本质的理解。

迭代法(BFS 队列实现)

递归的本质是栈的调用,而迭代可以用队列模拟递归过程,避免递归栈溢出的风险(尽管本题 n 较小时不明显)。

算法步骤

  1. 初始化队列,存储当前字符串和左右括号数量(如 ['', 0, 0]);
  2. 每次从队列取出一个元素,尝试添加 或 ,若合法则将新状态加入队列;
  3. 当字符串长度为 2n 时,将结果存入数组。

JavaScript 实现

function generateParenthesis(n) {
    const result = [];
    const queue = [['', 0, 0]]; // [current, left, right]
    while (queue.length > 0) {
        const [current, left, right] = queue.shift();
        if (current.length === 2 * n) {
            result.push(current);
            continue;
        }
        if (left < n) {
            queue.push([current + '(', left + 1, right]);
        }
        if (right < left) {
            queue.push([current + ')', left, right + 1]);
        }
    }
    return result;
}
// 示例:n=3
console.log(generateParenthesis(3)); 
// 输出:["((()))","(()())","(())()","()(())","()()()"]

复杂度分析

  • 时间复杂度:与递归法相同,O(4^n / sqrt(n)),需遍历所有合法组合。
  • 空间复杂度O(4^n / sqrt(n)),队列中最多存储所有中间状态。

优化技巧

在实际应用中,可以使用优先队列或广度优先搜索的变体来优化内存使用,可以限制队列中存储的状态数量,或者使用更高效的数据结构来管理中间状态。

动态规划(高效解法)

动态规划通过"自底向上"构建解,避免重复计算,适合需要多次调用或 n 较大的场景。

算法思路

定义 dp[i]i 对括号的有效组合集合,则:

  1. dp[0] = [""](空字符串);
  2. dp[i] 可由 dp[i-1] 推导:在 i-1 对括号的组合中,插入第 i 对括号,具体有两种方式:
    • 将 和 包裹在 dp[i-1] 的某个组合外(如 dp[1] = ["()"]dp[2] = ["(())"]);
    • 将 插入 dp[i-1] 的组合开头, 插入中间某个位置(如 dp[1] = ["()"]dp[2] = ["()()"])。

更准确的转移方程: dp[i] = "(" + x + ")" + yx 属于 dp[j]y 属于 dp[i-1-j]0 ≤ j < i-1

JavaScript 实现

function generateParenthesis(n) {
    const dp = Array.from({ length: n + 1 }, () => []);
    dp[0] = ['']; // 初始化:0 对括号只有空字符串
    for (let i = 1; i <= n; i++) {
        for (let j = 0; j < i; j++) {
            const leftParts = dp[j]; // 左边 j 对括号
            const rightParts = dp[i - 1 - j]; // 右边 i-1-j 对括号
            for (const left of leftParts) {
                for (const right of rightParts) {
                    dp[i].push('(' + left + ')' + right);
                }
            }
        }
    }
    return dp[n];
}
// 示例:n=3
console.log(generateParenthesis(3)); 
// 输出:["((()))","(()())","(())()

标签: #栈算法 #有效性检查 #字符串处理