22. 括号生成

22. 括号生成 - 力扣(LeetCode)

1、一个「合法」括号组合的左括号数量一定等于右括号数量,这个很好理解。

2、对于一个「合法」的括号字符串组合 `p`,必然对于任何 `0 <= i < len(p)` 都有:子串 `p[0..i]` 中左括号的数量都大于或等于右括号的数量。

思路

明白了合法括号的性质,如何把这道题和回溯算法扯上关系呢?

算法输入一个整数 `n`,让你计算 **`n` 对儿括号**能组成几种合法的括号组合,可以改写成如下问题:

现在有 `2n` 个位置,每个位置可以放置字符 `(` 或者 `)`,组成的所有括号组合中,有多少个是合法的?

这个命题和题目的意思完全是一样的对吧,那么我们先想想如何得到全部 `2^(2n)` 种组合,然后再根据我们刚才总结出的合法括号组合的性质筛选出合法的组合,不就完事儿了?

如何得到所有的组合呢?这就是标准的暴力穷举回溯框架:

cpp 复制代码
result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

那么对于我们的需求,如何打印所有括号组合呢?套一下框架就出来了,伪码如下:

cpp 复制代码
void backtrack(int n, int i, string& track) {
    // i 代表当前的位置,共 2n 个位置
    // 穷举到最后一个位置了,得到一个长度为 2n 组合
    if (i == 2 * n) {
        print(track);
        return;
    }

    // 对于每个位置可以是左括号或者右括号两种选择
    for choice in ['(', ')'] {
        track.push(choice); // 做选择
        // 穷举下一个位置
        backtrack(n, i + 1, track);
        track.pop(choice); // 撤销选择
    }
}

那么,现在能够打印所有括号组合了,如何从它们中筛选出合法的括号组合呢?很简单,加几个条件进行「剪枝」就行了。

对于 `2n` 个位置,必然有 `n` 个左括号,`n` 个右括号,所以我们不是简单的记录穷举位置 `i`,而是用 `left` 记录还可以使用多少个左括号,用 `right` 记录还可以使用多少个右括号,这样就可以通过刚才总结的合法括号规律进行筛选了。

代码

java 复制代码
public List<String> generateParenthesis(int n) {
    if (n == 0) return new ArrayList<>();
    // 记录所有合法的括号组合
    List<String> res = new ArrayList<>();
    // 回溯过程中的路径
    StringBuilder track = new StringBuilder();
    // 可用的左括号和右括号数量初始化为 n
    backtrack(n, n, track, res);
    return res;
}

// 可用的左括号数量为 left 个,可用的右括号数量为 rgiht 个
void backtrack(int left, int right, 
            StringBuilder track, List<String> res) {
    // 若左括号剩下的多,说明不合法
    if (right < left) return;
    // 数量小于 0 肯定是不合法的
    if (left < 0 || right < 0) return;
    // 当所有括号都恰好用完时,得到一个合法的括号组合
    if (left == 0 && right == 0) {
        res.add(track.toString());
        return;
    }
    
    // 尝试放一个左括号
    track.append('('); // 选择
    backtrack(left - 1, right, track, res);
    track.deleteCharAt(track.length() - 1); // 撤消选择

    // 尝试放一个右括号
    track.append(')'); // 选择
    backtrack(left, right - 1, track, res);
    track.deleteCharAt(track.length() - 1); // 撤消选择
}
相关推荐
lizhongxuan35 分钟前
AI小镇 - 涌现
算法·架构
码路飞2 小时前
GPT-5.4 Computer Use 实战:3 步让 AI 操控浏览器帮你干活 🖥️
java·javascript
AI工程架构师3 小时前
通常说算力是多少 FLOPS,怎么理解,GPU和CPU为什么差异这么大
算法
祈安_4 小时前
Java实现循环队列、栈实现队列、队列实现栈
java·数据结构·算法
皮皮林55116 小时前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
归去_来兮17 小时前
拉格朗日插值算法原理及简单示例
算法·数据分析·拉格朗日插值
顺风尿一寸19 小时前
从 Java NIO poll 到 Linux 内核 poll:一次系统调用的完整旅程
java
程途知微19 小时前
JVM运行时数据区各区域作用与溢出原理
java
华仔啊1 天前
为啥不用 MP 的 saveOrUpdateBatch?MySQL 一条 SQL 批量增改才是最优解
java·后端
千寻girling1 天前
Python 是用来做 AI 人工智能 的 , 不适合开发 Web 网站 | 《Web框架》
人工智能·后端·算法