给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[ "((()))", "(()())", "(())()", "()(())", "()()()" ]
方法一:暴力法
思路
我们可以生成所有 22n2^{2n}22n 个
'('
和')'
字符构成的序列。然后,我们将检查每一个是否有效。算法
为了生成所有序列,我们使用递归。长度为
n
的序列就是'('
加上所有长度为n-1
的序列,以及')'
加上所有长度为n-1
的序列。为了检查序列是否为有效的,我们会跟踪
平衡
,也就是左括号的数量减去右括号的数量的净值。如果这个值始终小于零或者不以零结束,该序列就是无效的,否则它是有效的。复杂度分析
时间复杂度:O(22nn)O(2^{2n}n)O(22nn),对于 22n2^{2n}22n 个序列中的每一个,我们用于建立和验证该序列的复杂度为 O(n)O(n)O(n)。
空间复杂度:O(22nn)O(2^{2n}n)O(22nn),简单地,每个序列都视作是有效的。请参见方法三以获得更严格的渐近界限。
方法二:回溯法
思路和算法
只有在我们知道序列仍然保持有效时才添加
'('
or')'
,而不是像方法一那样每次添加。我们可以通过跟踪到目前为止放置的左括号和右括号的数目来做到这一点,如果我们还剩一个位置,我们可以开始放一个左括号。 如果它不超过左括号的数量,我们可以放一个右括号。
复杂度分析
我们的复杂度分析依赖于理解
generateParenthesis(n)
中有多少个元素。这个分析超出了本文的范畴,但事实证明这是第n
个卡塔兰数 1n+1(2nn)\dfrac{1}{n+1}\binom{2n}{n}n+11(n2n),这是由 4nnn\dfrac{4^n}{n\sqrt{n}}n√n4n 渐近界定的。
时间复杂度:O(4nn)O(\dfrac{4^n}{\sqrt{n}})O(√n4n),在回溯过程中,每个有效序列最多需要
n
步。空间复杂度:O(4nn)O(\dfrac{4^n}{\sqrt{n}})O(√n4n),如上所述,并使用 O(n)O(n)O(n) 的空间来存储序列。
方法三:闭合数
思路
为了枚举某些内容,我们通常希望将其表示为更容易计算的不相交子集的总和。
考虑有效括号序列
S
的闭包数:至少存在index> = 0
,使得S[0], S[1], ..., S[2*index+1]
是有效的。 显然,每个括号序列都有一个唯一的闭包号。 我们可以尝试单独列举它们。算法
对于每个闭合数
c
,我们知道起始和结束括号必定位于索引0
和2*c + 1
。然后两者间的2*c
个元素一定是有效序列,其余元素一定是有效序列。复杂度分析
- 时间和空间复杂度:O(4nn)O(\dfrac{4^n}{\sqrt{n}})O(√n4n),该分析与方法二类似。
public class Solution {
public static void main(String[] args) {
System.out.println(generateParenthesis(3));
}
public static ArrayList<String> generateParenthesis(int n) {
ArrayList<String> list = new ArrayList<String>();
rec(n, 0, 0, "", list);
return list;
}
public static void rec(int n, int left, int right, String s, ArrayList<String> list){
// invariant必须满足左括号数目要大等于右括号数目
if(left < right){
return;
}
// 如果左右括号数目相等则添加到list
if(left==n && right==n){
list.add(s);
return;
}
// 左括号已满,只能添加右括号
if(left == n){
rec(n, left, right+1, s+")", list);
return;
}
rec(n, left+1, right, s+"(", list); // 继续添加左括号
rec(n, left, right+1, s+")", list); // 继续添加右括号
}
}