1. 程式人生 > 實用技巧 >括號生成

括號生成

數字 n 代表生成括號的對數,請你設計一個函式,用於能夠生成所有可能的並且有效的括號組合。

示例:

輸入:n = 3
輸出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

解法一

又臭又長,還超時。

 vector<string> ans;
    string brackets;
    map<string,int> m;
    bool isValid(string s){
        stack<char> st;
        if(s[0]==')') return false;
        st.push('(');
        for(int i=1;i<s.size();++i){
            //訪問棧頂元素要先檢查棧不空,之前沒有檢查導致好久才看出錯誤。
            if(s[i]==')'&&st.size()!=0&&st.top()=='(') {
                st.pop();
            }
            else if(s[i]==')') return false;
            else st.push('(');
        }
        return true;
    }
    void backtrack(int t,int n,string cur){
        if(t>=2*n) {
            if(m[cur]==0)
                ans.push_back(cur);
            m[cur]++;
            return;
        }
        for(int i=t;i<2*n;++i){
            swap(cur[i],cur[t]);
            if(!isValid(cur.substr(0,t+1))) return;
            else{
            backtrack(t+1,n,cur);
            swap(cur[i],cur[t]);
            }
        }
    }
    vector<string> generateParenthesis(int n) {
        for(int i=0;i<n;++i) brackets+="()";
        backtrack(0,n,brackets);
        return ans;
    }

花了好久才看出越界訪問的原因,原來是訪問棧頂元素前沒有檢查它是否為空:

分析:

這裡是用排列樹的思想,但實際上只有左括號和右括號兩種符號,因此不僅時間複雜度大而且會產生重複結果。

解法二

使用雙遞迴的方法:

 vector<string> ans;
    void backtrack(int left,int right,string cur){
        if(left==0&&right==0) {
            ans.push_back(cur);
            return;
        }
        //替換下面兩行順序,即先遍歷右子樹再遍歷左子樹也是可以的。
        if(left>0) backtrack(left-1,right,cur+'(');
        //只有當剩餘')'比'('多時才能新增')'。
        if(right>left) backtrack(left,right-1,cur+')');
    }
    vector<string> generateParenthesis(int n) {
        backtrack(n,n,"");
        return ans;
    }

tips:

只有在剩餘的右括號不少於左括號時才有可能符合要求。
**遇到這種每次只有兩種選擇的問題可以使用雙遞迴(二叉樹遍歷)方法。**
因為用二叉樹的思想,不同路徑對應不同結果,因此不會有重複結果。

分析:

只有左括號和右括號兩種選擇,可以考慮雙遞迴也即用二叉樹的思想。這種演算法實際上就是二叉樹的先序遍歷。當還剩左括號時遍歷左子樹(加左括號);當剩餘右括號多於左括號時遍歷右子樹(加右括號)。要注意的是隻有在剩餘右括號多於左括號時才能加右括號,否則得到的結果必然不符要求。

解法三

也是使用雙遞迴的方法,是對解法一的改進。

 vector<string> ans;
    bool isValid(const string& s){
        stack<char> st;
        if(s[0]==')') return false;
        st.push('(');
        for(int i=1;i<s.size();++i){
            //訪問棧頂元素要先檢查棧不空,之前沒有檢查導致好久才看出錯誤。
            if(s[i]==')'&&st.size()!=0&&st.top()=='(') {
                st.pop();
            }
            else if(s[i]==')') return false;
            else st.push('(');
        }
        return true;
    }
    void backtrack(int left,int right,string cur){
        if(left==0&&right==0) {
            if(isValid(cur))
                ans.push_back(cur);
            return;
        }
        //沒有檢查當前生成字串的合法性,而是把檢查過程放到了最後。
        if(left>0) backtrack(left-1,right,cur+'(');
        if(right>0) backtrack(left,right-1,cur+')');
    }
    vector<string> generateParenthesis(int n) {
        backtrack(n,n,"");
        return ans;
    }