22. 括号生成

1.题目介绍

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:
输入:n = 1
输出:["()"]

提示:
1 <= n <= 8

2.题解

2.1 暴力枚举

思路

我们利用递归枚举出n对括号(这里是n对,说明共有2n个括号)即共2^2n种情况,并在其中找到正确的括号匹配情况。
1.递归返回条件,一次次加入'('和')'后,最终字符串长度到达n之后,进行合法性检验,若正确,加入结果数组中
2.合法性检验,必须随时满足左边括号多于等于右边括号数量,最终两边括号数量必须相等。
3.递归过程,现将当前字符串加上'(',继续递归,后删除这个'(',再加上')'进行继续递归。
4.注意条件给的是n对,所以最开始填参数时,总数量应为2*n;

代码

class Solution {
private:
    bool vaild(string &curr){
        int balance = 0;
        for(char ch:curr){
            if (ch == '(') balance++;
            else balance--;
            if(balance < 0) return false;
        }
        return balance == 0;
    }
    void generateAll(vector<string> &ans, int n, string &current){
        if(n == current.size()){
            if(vaild(current)){
                ans.push_back(current);
            }   
            return;
        }
        current += '(';
        generateAll(ans,n,current);
        current.pop_back();
        current += ')';
        generateAll(ans,n,current);
        current.pop_back();
    }

public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        string current;
        generateAll(ans, 2*n, current);
        return ans;
    }
};

复杂度分析

2.2 回溯法

思路

方法2.1有一个问题,就是当已经出现了不满足情况的括号组合,他依旧会继续递归下去,直到达到总括号数才会进行合法性检验,这样就浪费了很多递归资源
我们这里在每次递归中加入两个标记位置,分别标记已有的左括号数和有括号数目,左括号数目open < n(总共2n,左边的肯定要<n, =n的时候再加就超出了),有括号数目close < open < n即可。

这里所谓的回溯就是,在遇到不满足递归条件的中途节点,不会继续递归,而是回溯到上一级节点。这里就是使用if判断直接跳过本次递归过程,函数完成后自动回溯上一级调用节点。

代码

class Solution {
private:
    void backtrack(vector<string> &ans, int n, int open, int close, string &curr){
        if(n*2 == curr.size()){
            ans.push_back(curr);
            return;
        }
        if(open < n){
            curr += '(';
            backtrack(ans, n, open + 1, close, curr);
            curr.pop_back();
        }
        if(close < open){
            curr += ')';
            backtrack(ans, n, open, close + 1, curr);
            curr.pop_back();
        }
    }
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        string curr;
        backtrack(ans, n, 0, 0, curr);
        return ans;
    }
};

复杂度分析

2.3 按括号序列的长度递归

思路


这里我们设计了一个函数generate,用于生成在长度为n(2*n)时,所有可能的合法括号序列。
任何一个合法的括号序列可以拆分为(a)b的结构,将一个大问题拆分为子问题,然后不断进行递归调用即可。
1.这里为何要使用共享指针shared_ptr?
1.1 主要是为了实现动态规划中的记忆化搜索来避免重复计算,将中间结果存储在缓存中,可以避免在递归过程中对相同问题的重复计算.
if(cache[n] != nullptr){ return cache[n];} 若结果成立,说明已存储中间结果,直接返回。这里便是减少了大量重复递归应用,节省资源时间。
举个例子:某一层递归 n = 8, i = 2, n -i -1 = 5; 递归得到了 2 和 5 对应的括号序列
之后再继续到了 i = 5, n - i - 1 = 2, 此时进入到2和5的递归,在指针数组中发现已经计算过了,直接返回即可!
1.2 可以灵活地分配和释放内存,只在需要时分配,避免不必要的空间占用
1.3 shared_ptr 的引用计数机制确保了在不再需要缓存时正确释放相关内存,比如像这里我在循环结束后并不希望释放指针result,而cache[n]与result指向同一块区域,虽然result消亡,但是cache[n]依然存在,引用计数不为零,并不会释放这个指针。

代码

class Solution {
    shared_ptr<vector<string>> cache[9] = {nullptr}; //这里虽然1<=n<=8,但是数组从索引0开始,所以要多加一个
public:
    shared_ptr<vector<string>> generate(int n) {
        if(cache[n] != nullptr){ return cache[n];} //递归剪枝
        if(n == 0) cache[0] = shared_ptr<vector<string>>(new vector<string>{""}); //当长度为0时,即一个空字符串,且必须是空字符串,空数组和空字符串是有区别的,空字符串数组中的一个元素,空数组是没有元素的!!!
        else{
            auto result = shared_ptr<vector<string>>(new vector<string>); //这里的字符串数组无需初始化,后面必定会进行添加的。(使用 result-> 解引用)
            /* 开始讨论(a)b中a和b的各种情况*/
            for(int i = 0; i != n; i++){
                auto lefts = generate(i); // 指针指向->a长度为i(2*i)时各种可能的合法情况集合    
                // 指针指向->b长度为n-i-1(2*(n-i-1))时各种可能的合法情况集合(这里的相当于 2*n(总括号数目)- 2((a)b中的两个括号) - 2 * i(a的括号数目) / 2)
                auto rights = generate(n-i-1); 
                for(auto &left: *lefts){
                    for(auto &right: *rights){
                        //相当于(*result). 这里不能使用cache[n]->注意此时其还是nullptr,需要我们加入result元素进去!!!
                        result -> push_back('('+ left + ')' + right); 
                    }
                }   
            }
            cache[n] = result; //当 result 超出作用域时,shared_ptr 会检查引用计数。由于 cache[n] 也指向相同的内存块,引用计数不为零,内存不会被释放。
        }
        return cache[n];
    }
    vector<string> generateParenthesis(int n) {
        return *generate(n); // 注意这里generate(n)返回的是一个指向vector<string>的指针,我们要返回的是一个数组,所以加上*
    }
};
posted @ 2024-01-23 15:49  DawnTraveler  阅读(2)  评论(0编辑  收藏  举报