问题是: 给定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));
}
}
结果不是我想要的: (((()))),((((()))))())),(((()))))())))()),((((())))) ())))()))(),(((()))))())))()))()))(())),((((()))))( ))))()))()()()))))()).........
有没有人告诉我这是什么问题?非常感谢你。
答案 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
)。
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)
另一种更简单的方法。 在这里,我正在尝试一种递归解决方案,其中递归函数以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.