了解函数以生成括号

时间:2014-05-01 18:17:31

标签: java algorithm

我有这个算法来生成格式正确的括号的所有组合。

有人可以解释算法的核心概念吗?我尝试通过它进行调试,但我仍然无法掌握算法背后的基础概念

此外,关于如何为这个问题提出这样一个算法的任何一般性建议,即如何通过这种方式如此聪明地解决它,或者为了达到这个阶段必须做什么练习。

问题:

  

给定n个括号对,编写一个要生成的函数       良好形成括号的所有组合。例如,给定n = 3,解决方案集为:

“((()))”, “(()())”, “(())()”, “()(())”, “()()()”

代码:

public ArrayList<String> generateParenthesis(int n) {
    ArrayList<String> solutions = new ArrayList<String>();
    recursion(n, new String(), solutions);
    return solutions;
}

private void recursion(int n, String str, ArrayList<String> sol) {
    if(str.length() == 2 * n)
        sol.add(str);
    else {
        int left = 0;
        int right = 0;
        for(int i = 0; i < str.length(); ++i) {
            if(str.charAt(i) == '(')
                left++;
            if(str.charAt(i) == ')')
                right++;
        }
        if(left == right)
            recursion(n, str + "(", sol);
        else if(right < left) {
            if(left < n)
                recursion(n, str + "(", sol);
            recursion(n, str + ")", sol);
        }
    }
}

10 个答案:

答案 0 :(得分:20)

它可以帮助我直观地看到呼叫是如何堆叠的。我在调用中添加了一个参数String depth,并在每次调用时打印出depth + str,为每个调用添加了四个空格给每个深度参数。这使我们能够很好地了解呼叫顺序。

以下是代码:

recursion(3, new String(), solutions, "");
//...
private static void recursion(int n, String str, ArrayList<String> sol, String depth) {
    System.out.println(depth + str);
    //...
        if(left == right)
            recursion(n, str + "(", sol, depth + "    ");
        else if(right < left) {
            if(left < n)
                recursion(n, str + "(", sol, depth + "    ");
            recursion(n, str + ")", sol, depth + "    ");
}

以下是打印出来的内容:

(
    ((
        (((
            ((()
                ((())
                    ((()))
        (()
            (()(
                (()()
                    (()())
            (())
                (())(
                    (())()
    ()
        ()(
            ()((
                ()(()
                    ()(())
            ()()
                ()()(
                    ()()()

每个递归级别都会为输出添加另一个缩进。如果两个输出处于相同的缩进级别,则它们都是从相同的递归级别调用的。

这是另一个视觉效果:

请注意,每个节点都是更深层次的递归,每次子节点直接从父节点出来时,它都不会分成两个递归路径。也就是说,父节点只调用recursion一次。

Colorful parentheses

答案 1 :(得分:7)

递归肯定会弄乱你的脑袋。这是另一种可能更容易理解的方法:

void generate() {
    ArrayList<String> results = new ArrayList<String>();
    generateParentheses(4, 0, new StringBuilder(), results);
    System.out.println(results);
}

void generateParentheses(final int remaining, final int openCount, final StringBuilder s, final List<String> results) {
    if (remaining == 0 && openCount == 0) {
        results.add(s.toString());
        return;
    }
    if (openCount > 0) { // we can close the open one
        s.append(")");
        generateParentheses(remaining, openCount-1, s, results);
        s.setLength(s.length()-1); // pop the last char off
    }
    if (remaining > 0) { // start a new one
        s.append("(");
        generateParentheses(remaining-1, openCount+1, s, results);
        s.setLength(s.length()-1); // pop the last char off
    }
}

输出为[()()()(), ()()(()), ()(())(), ()(()()), ()((())), (())()(), (())(()), (()())(), (()()()), (()(())), ((()))(), ((())()), ((()())), (((())))]

这是另一端的问题。你是如何想出这些模式的?

从对数(remaining)开始。

只有两种可能:开放或封闭。如果还有一些要追加,则只能附加一个空括号。如果要关闭相应的左括号,则只能附加一个近括号。

所以你只需要计算剩余的剩余数量,以及你的括号内有多深。让递归处理剩下的事情。

答案 2 :(得分:1)

这是我尝试分解所提供的算法:

if(str.length() == 2 * n)
        sol.add(str);

如果字符串长度是括号对的两倍,就知道你已经完成了。为什么?

  

因为每对括号的长度为2个字符,所以使用n个括号表示总共有2 * n个字符(即length / 2 == number of parentheses

int left = 0;
int right = 0;
for(int i = 0; i < str.length(); ++i) {
    if(str.charAt(i) == '(')
        left++;
    if(str.charAt(i) == ')')
        right++;
}

这将逐个字符循环字符串,并根据特定字符是开括号还是右括号进行一些测试和更改leftright。基于此,你能弄清楚循环结束时leftright是什么吗?

  

leftright分别是字符串中的开/关括号数

if(left == right)
    recursion(n, str + "(", sol);
else if(right < left) {
    if(left < n)
        recursion(n, str + "(", sol);
    recursion(n, str + ")", sol);
}

如果是left == right,那么你调用相同的函数,除了你在现有字符串的开括号上。为什么要添加一个括号,为什么它是一个左括号?

  

你知道你没有完成,因为如果你是的话,你会触发第一个if陈述。因为添加一个闭括号会导致格式不正确,因为没有未闭合的左括号(请记住,left == right),下一个逻辑步骤是添加一个左括号。

如果right < left,您知道至少有一个未闭合的左括号。所以你想再做一次检查。

if(left < n)
    recursion(n, str + "(", sol)

为什么要检查?如果left < n,那么您知道如果您关闭所有打开的括号,则您将没有足够的对(n对括号n开括号。所以不妨添加另一个左括号!

最后一个语句与其关联的隐式else。如果left不是< n,那么您知道添加另一个左括号会使您超过所请求的对数。所以添加一个右括号并继续。

答案 3 :(得分:1)

核心概念:

1)X:如果一个字符串有更多的括号括号,那么在右边添加更多的括号不会使它成为一个格式良好的组合。

2)所有格式良好的组合具有相同数量的开括号和右括号。因此,每种类型都可以精确找到n次

3)如果结束的数量小于开数的数量,那么我们总是可以通过添加更多的右括号来形成良好的结果。

此算法构建组合,向右侧添加新符号

       public ArrayList<String> generateParenthesis(int n) {
                ArrayList<String> solutions = new ArrayList<String>();
                recursion(n, new String(), solutions);
                return solutions;
            }

            private void recursion(int n, String str, ArrayList<String> sol) {
            //If we got a sting long enough, we return it. This means a) We generate all 
//strings only once. b)If a string of length 2*n is created, then it is correct. Other
//code should satisfy these conditions
                if(str.length() == 2 * n) 
                    sol.add(str);
                else {
                    int left = 0;
                    int right = 0;
                    for(int i = 0; i < str.length(); ++i) {
                        if(str.charAt(i) == '(')
                            left++;
                        if(str.charAt(i) == ')')
                            right++;
                    }
                    //left and right are now numbers of parentheses in the string. 
//Opening and closing respectively.
                    if(left == right)//On each step we maintain the condition X 
//that the number of closing brackets is less or equal to the number of opening.
//Therefore, is their numbers are equal we can only add opening ones
                        recursion(n, str + "(", sol);
                    else if(right < left) { // if X is maintained and we can add 
//both types
                        if(left < n)// The number of opened should be no more than n, 
//according to 2)
                            recursion(n, str + "(", sol);
                        recursion(n, str + ")", sol);//The number of closing ones is 
//limited by X by the number of opening ones, which is limited by n => the number of 
//the closed ones is limited by n => we can add them as long as it doesn't violate X
                    }

                }
            }

答案 4 :(得分:0)

随着您获得更多递归体验,您将更容易看到解决方案。

想法:格式正确的括号总是从左边的paren开始,左边有一个相同的数字是的,从左到右阅读时,以下内容始终为真left >= right

因此,在制作递归解决方案时,我们将使用一个简单的规则:prefer to open left paren并让递归函数展开的性质解决其余问题

private void recursion(int n, String str, ArrayList<String> sol) {
    if(str.length() == 2 * n)
        sol.add(str);

我们有n对,添加解决方案&amp;返回

    else {
        int left = 0;
        int right = 0;
        for(int i = 0; i < str.length(); ++i) {
            if(str.charAt(i) == '(')
                left++;
            if(str.charAt(i) == ')')
                right++;
        }

计算左边和右边的数字正确的parens

        if(left == right)
            recursion(n, str + "(", sol);

str目前是平衡的&amp;因为我们更喜欢左右,添加左侧

        else if(right < left) {

这可能只是一个别人,权利永远不会是&gt;剩下。无论哪种方式,这意味着我们目前处于不平衡状态,左右至少还有1个。

            if(left < n)
                recursion(n, str + "(", sol);

检查你是否可以再添加一个左,再次因为偏向右边

            recursion(n, str + ")", sol);
        }
    }

添加我们的权利。这将关闭在它上面的行中添加的paren,或者如果没有执行,它将关闭前一个左边(请记住我们在这个块中因为它目前是不平衡的)<\ n / p>

答案 5 :(得分:0)

这是一个更简单直观的解决方案。

同样,这遵循递归的想法,但它比你发布的更容易阅读和更有效。

public void generateParantheses(int n){
    helper(n,0,0,"");
}
public void helper(int n, int open, int close, String result){

    if(result.length()==2*n) { 
    // easy enough to understand? one open and one close for each n?
        System.out.println(result);
        return;
    }
    if(open<n){
        //start off with all n open parantheses
        helper(n, open+1, close, result+"(" );
    }
    if(close<open){
        // now check if we can add closing parantheses on top of open in this condition
        helper(n, open, close+1, result+")");
    }
}

答案 6 :(得分:0)

我用不同的风格编写了自己的递归括号生成器。它基本上构建了一个String,但是在每次递归调用时,都会创建一个新的String,以便回溯正确。我希望有人觉得它有用。

import java.util.ArrayList;
import java.util.List;

public class GenerateParentheses {

    // N: The max number of matching parentheses. This value does not change.
    // usedL, usedR : Number of left and right parentheses already used in 'current' string.
    // current: the current string being built.
    // depth: recursion depth, used for pretty-printing
    public static void generate(int N, int usedL, int usedR, String current, List<String> result, int depth) {

        System.out.printf("%susedL=%d, usedR=%d, current='%s'\n",
                    getIndentation(depth), usedL, usedR, current);

        if (usedL == N && usedR == N) {
            // We've used up all the available parentheses (up to N),
            // so add the current built string to the result.
            result.add(current);
            return;
        }

        if (usedL < N) {
            // Add another left parenthesis "(".
            String newCurrent = current + "(";
            generate(N, usedL + 1, usedR, newCurrent, result, depth+1);
        }

        if (usedR < N && usedL > usedR) {
            // Add another right parenthesis ")" if there are already 
            // used left parentheses.
            String newCurrent = current + ")";
            generate(N, usedL, usedR + 1, newCurrent, result, depth+1);
        }
    }

    // Utility function used for pretty-printing.
    private static String getIndentation(int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("  ");
        }
        return sb.toString();
    }

    public static void main(String argv[]) {

        int N = 3;
        int usedL = 0;
        int usedR = 0;
        String current = "";
        List<String> result = new ArrayList<String>();
        int depth = 0;

        generate(N, usedL, usedR, current, result, depth);

        for (String s : result) {
            System.out.printf("%s\n", s);
        }
    }
}

这是输出:

usedL=0, usedR=0, current=''
  usedL=1, usedR=0, current='('
    usedL=2, usedR=0, current='(('
      usedL=3, usedR=0, current='((('
        usedL=3, usedR=1, current='((()'
          usedL=3, usedR=2, current='((())'
            usedL=3, usedR=3, current='((()))'
      usedL=2, usedR=1, current='(()'
        usedL=3, usedR=1, current='(()('
          usedL=3, usedR=2, current='(()()'
            usedL=3, usedR=3, current='(()())'
        usedL=2, usedR=2, current='(())'
          usedL=3, usedR=2, current='(())('
            usedL=3, usedR=3, current='(())()'
    usedL=1, usedR=1, current='()'
      usedL=2, usedR=1, current='()('
        usedL=3, usedR=1, current='()(('
          usedL=3, usedR=2, current='()(()'
            usedL=3, usedR=3, current='()(())'
        usedL=2, usedR=2, current='()()'
          usedL=3, usedR=2, current='()()('
            usedL=3, usedR=3, current='()()()'
((()))
(()())
(())()
()(())
()()()

答案 7 :(得分:0)

public static void findParenthisis(String s , int left ,int right){
        if(left==right && left == 0){
            System.out.println(s);
        }
        if(left > 0){
            findParenthisis(s+'(',left-1,right);
        }
        if(left < right){
            findParenthisis(s + ')',left,right-1);
        }
    }

答案 8 :(得分:0)

这是C ++中的简单递归版本。该解决方案不适用于LeetCode,因为最终列表的顺序不正确。仍然可以。

vector<string> generateParenthesis(int n) {
    if (n == 1) return vector<string>({"()"}); 

    vector<string> sub_paran = generateParenthesis(n - 1); 

    vector<string> paran; 
    for (string s : sub_paran) {
        paran.push_back("(" + s + ")");
        string right = s + "()";
        string left = "()" + s; 
        if (right == left) paran.push_back(right); 
        else {
            paran.push_back(right); 
            paran.push_back(left); 
        }
    }

    return paran; 
}

答案 9 :(得分:0)

    void generateParenthesis(int open, int close, int position, int n, char[] str) {
    /*
     * open  = open parenthesis
     * close = close parenthesis
     * position   = 2*n (length of combination of valid parenthesis
     * n     = pair of parenthesis
     * Algorithm:
     * 1.Check if position == 2*n --  Yes, print the str
     * 2.check if open is less than n 
     *      If true, add a open parenthesis into str and  call the function recursively by 
     *      incrementing open by 1 and position by 1
     * 3.check if close < open
     *      If true , add a close parenthesis and call the function recursively by 
     *      incrementing close by 1 and position by 1*/


    if(position ==str.length) {
        for(int i=0;i<str.length;i++) {
            System.out.print(str[i]);
        }
        System.out.println();
        return;
    }
    if(open < n) {
        str[position] = '(';
        generateParenthesis(open+1,close,position+1,n,str);

    }if(close< open) {
        str[position]=')';
        generateParenthesis(open,close+1,position+1,n,str);
    }

}