leetcode22.括号生成

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

完整代码

当初的想法:
生成n个括号的排列组合,可以在生成n-1个括号的基础上进行处理,将括号分别加在其上一步实现的左面,右面,两边。
但是这会漏掉一些情况:当n=4时,“(())(())”这种情况会漏掉。

改进当初的想法:
生成n个括号的组合,依然是基于前面的结果进行处理,当初的想法只用到了第n-1步的值,导致结果会漏掉一些情况。这次不仅仅用到第n-1步的值,还会用到其他的结果,那就需要在求解的过程中将所有的结果保存。

其实本质思想是:动态规划

  • 【第n步的结果】=’(’+【第p步的结果】+’)’+【第q步的结果】(说明:p+q=n-1)
  • p:从0增加到n-1
  • q:从n-1减小到0
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<vector<string>> cell;
        vector<string> c;
        if(n<=0)
            return c;
        //为了便于处理,真正的存储结果从cell的第一行开始
        cell.push_back(c);//第0行保存空值
        c.push_back("()");
        cell.push_back(c);//第一行是n=1时的结果
        for(int i=2;i<=n;++i){           
            string temp="";
            c.clear();
            int p,q;            
            for(p=0;p<i;++p){
                if(p==0){                                        
                    q=i-1;
                    for(int k=0;k<cell[q].size();++k){//针对等于q时的每一个字符串进行处理
                        temp="()"+cell[q][k];
                        c.push_back(temp);//将当前情况下的结果保存
                    }
                }
                else{
                    for(int j=0;j<cell[p].size();++j){//针对等于p时的每一个字符串进行处理
                        temp='('+cell[p][j]+')';
                        if(p==i-1){//不再连接q;
                            c.push_back(temp);
                            continue;
                        }
                        q=i-1-p;
                        for(int k=0;k<cell[q].size();++k){//针对等于q时的每一个字符串进行处理
                            c.push_back(temp+cell[q][k]);//将当前情况下的结果保存
                        }
                    }
                }
                
            }
            cell.push_back(c);
        }
        return cell[n];
    }
};

比较精炼的写法
这里cell在n=0时,存入的是一个空串,大大方便了处理过程

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<vector<string>> cell;
        vector<string> c;
        if(n<=0)
            return c;
        //为了便于处理,真正的存储结果从cell的第一行开始
        // c.push_back("");
        // cell.push_back(c);//第0行保存空值
        // c.clear();//一定要将c中的内容清除后,再来存放新的值
        // c.push_back("()");
        // cell.push_back(c);//第一行是n=1时的结果
        /等价写法
        cell.push_back({""});//第0行
        cell.push_back({"()"});//第一行
        
        for(int i=2;i<=n;++i){           
            string temp="";
            c.clear();
            int p,q;            
            for(p=0;p<i;++p){   
                    cout<<"p="<<p<<" "<<cell[p].size()<<endl;
                    for(int j=0;j<cell[p].size();++j){//针对等于p时的每一个字符串进行处理
                        temp='('+cell[p][j]+')';
                        q=i-1-p;
                        cout<<"q="<<q<<" "<<cell[q].size()<<endl;
                        for(int k=0;k<cell[q].size();++k){//针对等于q时的每一个字符串进行处理
                            string str=temp+cell[q][k];                            
                            c.push_back(str);//将当前情况下的结果保存
                        }
                    }
                
                
            }
            cell.push_back(c);
        }
        return cell[n];
    }
};

特别注意:下面这种写法就不适合先存到一个vector的变量c中,再将c push_back到cell中,因为cell预先分配了空间。

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> c;
        if(n<=0)
            return c;
        vector<vector<string>> cell(n+1);        
        cell[0]={""};
        cell[1]={"()"};
        for(int i=2;i<=n;++i){           
            c.clear();                      
            for(int p=0;p<i;++p){
                for(string t1:cell[p]){//针对等于p时的每一个字符串进行处理 
                    int q=i-1-p;
                    for(string t2:cell[q]){//针对等于q时的每一个字符串进行处理
                        cell[i].push_back("("+t1+")"+t2);//将当前情况下的结果保存
                    }
                 }                
            }                     
        }        
        return cell[n];
    }
};

基本思想:回溯思想
参考:回溯

构建深度优先树:

  • 初始状态:有n个左括号,n个右括号
  • 产生左分支的条件:剩余左括号数>0时。只受自己剩余括号的数量的约束
  • 产生右分支的条件:剩余右括号数>0,且剩余的右括号数>=左括号数(等于也包含在内的原因是:初始情况能有分支产生,这也就引发了剪枝的出现,其实初始情况产生的分支也不会 满足条件,改成>,代码也一样通过)。同时会受左括号的约束
  • 剪枝的条件:剩余的右括号数<左括号数
  • 终止条件:剩余左右括号树都等于0时,该分支结束
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        //采用回溯的思想
        int left=n,right=n;
        vector<string> res;
        if(n<=0)
            return res;
        string c="";
        fun(left,right,res,c);
        return res;
    }
private:
    void fun(int left,int right,vector<string> &res,string &c){
        if(left==0&&right==0){//左右括号数剩余为0时,保存结果
            res.push_back(c);
            //cout<
            return;
        }
        else if(right<left){//剩余的右分支数<左分支数,剪枝
            return;
        }
        
        if(left>0){//剩余左括号数>0,生成左分支
            c+='(';
            fun(left-1,right,res,c);
            c.pop_back();//重置为原来的状态
        }
        if(right>0&&right>=left){//剩余右括号数>0,且剩余右括号数>左括号数,生成右分支
            c+=')';
            fun(left,right-1,res,c);
            c.pop_back();//重置为原来的状态
        }
    }
};

说明:

  1. 上述剪枝的条件判断提高了代码的执行效率,如果没有剪枝,代码执行起来时间会用的更长。
  2. 上述回溯使用的是减法的形式,左右括号还剩几个可以用;同样,也可以使用加法的形式,左右括号使用了几个,代码逻辑完全一样,只不过中间判断的条件有变化。

二刷
回溯

  • 回溯过程中统计左右括号的个数以及所组合成的括号的情况(中间变量以及结果变量)
  • 递归结束的条件:
    右括号的个数大于左括号的个数
    左括号或者右括号的个数大于n
    左括号和右括号的个数等于n,这种情况顺便保存到结果中
  • 递归过程:要么加上个左括号,要么加上个右括号
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        if(n < 1)
            return vector<string>();       
        vector<string> res;
        unordered_set<string> temp;
        dfs(temp, "", 0, 0, n);
        for(auto t : temp)
            res.push_back(t);
        return res;
    }
private:
    void dfs(unordered_set<string>& res, string temp, int l, int r, int n){
        if(r > l || r > n || l > n)
           return;
        if(r + l == 2 * n){
            res.insert(temp);
            return;
        }
        dfs(res, temp + '(', l + 1, r, n);
        dfs(res, temp + ')', l, r + 1, n);
    }
    
};
回溯代码模板:
void (变量,&结果变量){
	递归结束条件
	当前情况下可能的选择
}

BFS:
广度优先遍历

struct Node{
    string str;
    int l;
    int r;
    Node(string s, int i, int j): str(s), l(i), r(j){}
};
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        //bfs
        vector<string> res;
        queue<Node> q;
        Node t("", 0 ,0);
        q.push(t);
        while(!q.empty()){
            auto t = q.front();
            q.pop();
            if(t.l + t.r == 2 * n){
                res.push_back(t.str);
                continue;
            }                
            if(t.l + 1 <= n){
                q.push(Node(t.str + '(', t.l + 1, t.r));
            }
            if(t.r + 1 <= n && t.r + 1 <= t.l){
                q.push(Node(t.str + ')', t.l, t.r + 1));
            }
        }
        return res;
    }
};

动态规划:

  • n个括号的生成结果基于0……n - 1个括号的生成结果
  • res[n][i] = “(” + res[p][j] + “)” + res[q][k] (p + q = n - 1)
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        //动态规划:基于之前的结果处理
        vector<vector<string>> res(n + 1);
        res[0] = {""};
        res[1] = {"()"};
        for(int i = 2; i <= n; ++i){
            for(int p = 0; p < i; ++p){
                for(auto s : res[p]){
                    int q = i - p - 1;
                    for(auto t : res[q]){
                        res[i].push_back("(" + s + ")" + t);
                    }
                }
            }
        }
        return res[n];
    }
};

你可能感兴趣的:(#,算法,#,字符串)