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); // 撤消选择
}
相关推荐
ZZZ_O^O25 分钟前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
CV-King1 小时前
opencv实战项目(三十):使用傅里叶变换进行图像边缘检测
人工智能·opencv·算法·计算机视觉
ok!ko1 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
代码雕刻家1 小时前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain1 小时前
算法 | 位运算(哈希思想)
算法
2402_857589361 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰2 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没3 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
Kalika0-03 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
编程、小哥哥3 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring