在java中生成平衡括号

时间:2014-07-11 18:27:15

标签: java algorithm

问题是: 给定n对括号,编写一个函数来生成格式正确的括号的所有组合。

例如,给定n = 3,解集是:

"((()))","(()())","(())()",&# 34;()(())","()()()"

我曾经使用字符串解决这个问题如下:

public class Solution {
public List<String> generateParenthesis(int n) {

    ArrayList<String> result = new ArrayList<String>();
    //StringBuilder s = new StringBuilder();
    generate(n, 0, 0, "", result);
    return result;
}
public void generate(int n, int left, int right, String s, ArrayList<String> result){

    //left is num of '(' and right is num of ')'
    if(left < right){
        return;
    }
    if(left == n && right == n){
        result.add(s);
        return;
    }
    if(left == n){
        //add ')' only.
        generate(n, left, right + 1, s + ")", result);
        return;
    }
    generate(n, left + 1, right, s + "(", result);
    generate(n, left, right + 1, s + ")", result);
    }
}

现在我想用StringBuilder解决这个问题,代码是这样的:

import java.util.ArrayList;

public class generateParentheses {
public static ArrayList<String> generateParenthesis(int n) {

    ArrayList<String> result = new ArrayList<String>();
    StringBuilder sb = new StringBuilder();

    generate(n, 0, 0, result, sb);
    return result;
}

public static void generate(int n, int left, int right, ArrayList<String> result,
        StringBuilder sb) {

    if (left < right) {
        return;
    }
    if (left == n && right == n) {
        result.add(sb.toString());
        sb = new StringBuilder();
        return;
    }
    if (left == n) {
        generate(n, left, right + 1, result, sb.append(')'));
        return;
    }
    generate(n, left + 1, right, result, sb.append('('));
    //sb.delete(sb.length(), sb.length() + 1);
    generate(n, left, right + 1, result, sb.append(')'));
    //sb.delete(sb.length(), sb.length() + 1);
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    System.out.println(generateParenthesis(4));
    }
}

结果不是我想要的: (((()))),((((()))))())),(((()))))())))()),((((())))) ())))()))(),(((()))))())))()))()))(())),((((()))))( ))))()))()()()))))()).........

有没有人告诉我这是什么问题?非常感谢你。

5 个答案:

答案 0 :(得分:4)

你关闭。你的错误:

  • 尝试重置sb,而不是仅删除其最后一个字符
  • 您要重置sb的方式:

    • 使用sb = new StringBuilder();您重新分配sb这是当前方法的局部变量,而不是调用它的方法的变量(Java不是通过参考传递但是通过按值)。
    • 您的几乎正确的尝试文件评论sb.delete(sb.length(), sb.length() + 1);但在这里您实际上是尝试从位置sb.length()开始删除字符,但就像StringBuilder中字符的数组索引来自{{1}直到0所以试图在最后一个字符后删除一个字符,这个字符实际上无法删除任何内容。

      您需要的是

      sb.length() - 1

      或更具可读性

      sb.delete(sb.length() - 1, sb.length());
      

      但在性能方面可能是最好的方法sb.deleteCharAt(sb.length() - 1); (在答案底部描述)

      setLength
  • 的逻辑从StringBuilder中删除字符时也有缺陷

    • 您只在一个结束(回溯)递归调用的地方执行此操作:找到正确的结果后。但是其他情况如sb.setLength(sb.length() - 1); 呢?最重要的是,如果方法正常结束 ,如

      if (left < right)

      此处generate(3, 1, 1, ')'); generate(3, 1, 2, ')');//here we stop and backtrack 结束并删除generate(3, 1, 2, ')');中的最后一个字符,但不应该先前的方法sb删除自己添加到StringBuilder的generate(3, 1, 1, ')')吗?

    换句话说,你不应该只在递归调用的成功条件结束时删除最后一个字符,但是在每次递归调用之后,要确保该方法也会删除它添加的字符。

所以将代码更改为

)

或尝试写一些可能更具可读性的内容,如

public static void generate(int n, int left, int right, ArrayList<String> result,
        StringBuilder sb) {

    if (left < right) {
        return;
    }
    if (left == n && right == n) {
        result.add(sb.toString());
        return;
    }
    if (left == n) {
        generate(n, left, right + 1, result, sb.append(')'));
        sb.deleteCharAt(sb.length() - 1);// <--
        return;
    }
    generate(n, left + 1, right, result, sb.append('('));
    sb.deleteCharAt(sb.length() - 1);// <--
    generate(n, left, right + 1, result, sb.append(')'));
    sb.deleteCharAt(sb.length() - 1);// <--
}

但是在调用时需要将public static void generate(int maxLength, int left, int right, ArrayList<String> result, StringBuilder sb) { if (left + right == maxLength) { if (left == right) result.add(sb.toString()); } else if (left >= right) { generate(maxLength, left + 1, right, result, sb.append('(')); sb.deleteCharAt(sb.length() - 1); generate(maxLength, left, right + 1, result, sb.append(')')); sb.deleteCharAt(sb.length() - 1); } } 设置为maxLength,因为它是StringBuilder应包含的最大长度,因此您还必须将2*n更改为:

generateParenthesis(int n)

进一步改进:

如果您的目标是提高性能,那么您可能不想使用public static ArrayList<String> generateParenthesis(int n) { ArrayList<String> result = new ArrayList<String>(); StringBuilder sb = new StringBuilder(2 * n); generate(2 * n, 0, 0, result, sb); // ^^^^^ return result; } delete,因为每次创建新数组并使用其中没有任何值的数据副本填充它#39; t想要。

相反,您可以使用deleteCharAt方法。如果您将传递小于当前存储字符数的值,则会将setLength设置为此方法中传递的值,这将有效地使其后面的字符无关紧要。换句话说,这些字符将不再用于例如count,即使它们仍然在StringBuilder缓冲区数组中。

示例:

toString()
  • 在第1行StringBuilder sb = new StringBuilder("abc"); // 1 sb.setLength(2); // 2 System.out.println(sb); // 3 sb.append('d'); // 4 System.out.println(sb); // 5 将为至少StringBuilder个字符分配数组(默认情况下,它使用3来确定它现在将存储的缓冲字符数组的大小)并将从传递的字符串中放置字符,因此它将包含

    str.length() + 16

    应放置下一个字符的位置索引存储在['a', 'b', 'c', '\0', '\0', ... , '\0'] ^^^ - it will put next character here 字段中,现在它等于count

  • 在第2行中,3的值将设置为count,但我们的数组不会更改,因此它仍然会显示为

    2
  • 在第3行中,将创建并打印新的String,但只会在索引存储在['a', 'b', 'c', '\0', '\0', ... , '\0'] ^^^ - it will put next character here 之前放置字符,这意味着它只包含count和{ {1}}(数组仍然保持不变)。

  • 在第4行中,您将向缓冲区添加新字符,它将被放置在&#34; important&#34;之后。字符。由于重要字符的数量存储在a字段中(并且它们位于数组的开头),因此下一个不相关的字符必须位于b指向的位置,这意味着count将被放置在索引count的位置,这意味着现在数组看起来像

    d

    并且2的值会增加(我们只添加了一个字符,因此['a', 'b', 'd', '\0', '\0', ... , '\0'] ^^^ - it will put next character here 现在将成为count)。

  • 在第5行中,我们将创建并打印包含count使用的数组中第一个3字符的字符串,以便我们看到3

答案 1 :(得分:1)

在仔细推出这个程序后,我发现了问题。正确的代码如下所示:

import java.util.ArrayList;

public class generateParentheses {
    public static ArrayList<String> generateParenthesis(int n) {

    ArrayList<String> result = new ArrayList<String>();
    StringBuilder sb = new StringBuilder();

    generate(n, 0, 0, result, sb);
    return result;
}

public static void generate(int n, int left, int right, ArrayList<String> result,
        StringBuilder sb) {

    if (left < right) {
        return;
    }
    if (left == n && right == n) {
        result.add(sb.toString());
        //sb.delete(0,sb.length());
        return;
    }
    if (left == n) {
        generate(n, left, right + 1, result, sb.append(')'));
        //delete current ')'.
        sb.delete(sb.length() - 1, sb.length());
        return;
    }

    generate(n, left + 1, right, result, sb.append('('));
    //delete current '(' after you finish using it for next recursion.
    sb.delete(sb.length() - 1, sb.length());
    generate(n, left, right + 1, result, sb.append(')'));
    //same as above here.
    sb.delete(sb.length() - 1, sb.length());
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    System.out.println(generateParenthesis(4));


    }

}

结果是: (((()))),((()())),((())()),((()))(),(()(())),(()() ()),(()())(),(())(()),(())()(),()((())),()(()()),( )(())(),()()(()),()()()()

答案 2 :(得分:0)

参数,分配和返回的组合:

..., StringBuilder sb,...
    //...
    sb = new StringBuilder();
    return sb;

没有意义,因为参数是按值传递的,即sb是一个局部变量,其变化对调用环境没有影响。

如果要清除StringBuilder,有方法sb.delete(start,end)将真正影响通过sb引用的对象。

答案 3 :(得分:0)

enter image description here

另一种更简单的方法。 在这里,我正在尝试一种递归解决方案,其中递归函数以3种方式添加平衡括号:“()” + result and result +“()”和“(” + result +“)”对于函数返回的HashSet中的每个项目它调用然后将它们放入HashSet中以删除重复项。

     import java.util.HashSet;
     import java.util.Set;

     public class BalancedParanthesis {
       public static void main(String args[]){
         int noOfBrackets = 3;
         HashSet<String> hs=new HashSet(generate(noOfBrackets));
         System.out.println(hs);
         }
         public static HashSet<String> generate(int in)
         {
          HashSet<String> hs= new HashSet<String>();
          if(in ==1)
         {
        hs.add("()");
        return hs;
         }
        else{
           Set<String> ab=generate(in-1);
           for(String each:ab)
            {
            hs.add("("+each+")");
            hs.add("()"+each);
            hs.add(each+"()");
            }
        return hs;
         }
        }
       }

答案 4 :(得分:-1)

您的代码效率低下。这是一个更好的方法。我们通过大量修剪在2 ^ N个可能的括号排列上形成有效的暴力(如果当前参数没有可能的有效解,我们立即退出递归)。 这是C ++中的代码:

#include <iostream>
#include <vector>
#include <string>
using namespace std;
string result;
vector<string> solutions;
int N = 10;
void generateBrackets(int pos, int balance)
{
    if(balance > N-pos) return;
    if(pos == N)
    {
        //we have a valid solution
        //generate substring from 0 to N-1
        //and push it to the vector
        string currentSolution;
        for(int i = 0; i < N; ++i)
        {
            currentSolution.push_back(result[i]);
        }
        solutions.push_back(currentSolution);
        return;
    }
    result[pos] = '(';
    generateBrackets(pos+1, balance+1);
    if(balance > 0)
    {
        result[pos] = ')';
        generateBrackets(pos+1, balance-1);
    }
}
int main()
{
    result.assign(N, 'a');
    generateBrackets(0, 0);
    cout<<"Printing solutions:\n";
    for(int i = 0; i < solutions.size(); ++i)
    {
        cout<<solutions[i]<<endl;
    }
    return 0;
}

一些澄清:pos标记了我们正在生成的括号解决方案中的当前位置。如果我们到达位置N(这意味着解决方案是有效的),我们在string result变量中有一个有效的解决方案,我们只需使用有效的解决方案将其推送到向量中。现在我们可以问你使用的余额。我们观察到,为了使括号排列有效,在任何给定位置的任何时间,'(从开头必须大于'的计数)的计数和余额我们衡量它们的差异,所以只有在余额&gt;时,我才会在给定位置加上')'。 0.