生成括号对儿 - Generate Parenthesis - Leetcode - Medium
题目
Given n
pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
I/O 栗子
Example 1:
Input: n = 3 Output: ["((()))","(()())","(())()","()(())","()()()"]
Example 2:
Input: n = 1 Output: ["()"]
约束条件
-
1 <= n <= 8
解法1 动态规划
这是一种很多题目都可以利用的方式,但这里其实并不是一种最好的方式因为如果将problem分为sub-problem在解决可能在这里会出现重复的路径,但如果加入记忆的机制(利用哈希表或者list等结构)来进行存储等话可以来降低时间复杂度。
其实可以和斐波那契数列一样,比如 f(2) = f(1) + f(0) , base case : f(1) = 1 & f (0) = 0;
这个括号也可以这样生成:
f(0): ""
f(1): "("f(0)")"
f(2): "("f(0)")"f(1), "("f(1)")"
f(3): "("f(0)")"f(2), "("f(1)")"f(1), "("f(2)")"
所以 f(n) = "("f(0)")"f(n-1) , "("f(1)")"f(n-2) "("f(2)")"f(n-3) ... "("f(i)")"f(n-1-i) ... "(f(n-1)")"
参考代码
public class Solution { public List<String> generateParenthesis(int n) { List<List<String>> lists = new ArrayList<>(); lists.add(Collections.singletonList("")); for (int i = 1; i <= n; ++i) { final List<String> list = new ArrayList<>(); for (int j = 0; j < i; ++j) { for (final String first : lists.get(j)) { for (final String second : lists.get(i - 1 - j)) { list.add("(" + first + ")" + second); } } } lists.add(list); } return lists.get(lists.size() - 1); } }
复杂度解析
- 时间复杂度:和方法二一致。
解法2 递归
根据递归的思路:
-
定义终结的case。
-
定义出错的case。
-
找到不同的决策分支并调用函数自己。
我们只在知道它仍然是有效序列时添加它们。我们可以通过定位目前为止放置的左括号和右括号的数量来做到这一点。感觉语言有点不好描述,我画个图来解释一下吧,例如想生成3对儿括号,期待结果是
[
"((()))",
"(()())" ,
"(())()" ,
"()(())" ,
"()()()"
]
用下面我画的丑图来进行理解(请谅解):
以输入的n=3(组出三组括号)为栗子
类似快排或者归并排序的思路,其实可能应该叫做回溯(backtrack),每次找完一种分支情况就切换下一支,只是说这里代码里可能需要用递归解决。
我的代码
class Solution { private LinkedList<String> list = new LinkedList<String>(); public List<String> generateParenthesis(int n) { generator(n - 1,n, "("); return list; } private void generator(int countOpen, int countCloe, String generated) { if(countOpen == 0 && countCloe == 0 ) { list.add(generated); return; } if ( countOpen < countCloe) { generator(countOpen, countCloe - 1, generated + ")"); if(countOpen > 0 ) generator(countOpen -1 , countCloe, generated + "("); return; } if(countOpen == countCloe) { generator(countOpen - 1 , countCloe , generated + "("); return; } } }
复杂度解析
- 时间复杂度:回溯的特征是在递归的每一层都可以做出许多b次决策。如果你把递归树形象化,这是每个内部节点的子节点的数量。你也可以把b看成是底,这可以帮助你记住b是指数的底。如果我们可以在递归的每一层做b个决策,并且我们将递归树扩展到d层(即:每条路径的长度为d),那么我们就得到b^d个节点。由于回溯是彻底的并且必须访问这些节点中的每一个,所以时间复杂度是O(b^d)。这里的话b=2,d的话看n所以为O(2^n),但我也看见了有说法说是O(4的n次方/n的开方),其实都是趋近的没有冲突。
解法3 暴力解法
暴力解法需要两个函数,一个函数判断字符串是否为合法括号对,另一个穷举出所有的括号组成可能。
为了生成所有序列,我们使用递归。所有长度为n的序列就是"("加上所有长度为n-1的序列,然后所有长度为n-1的序列加上")" 。
为了检查一个序列是否有效,我们跟踪一个平衡值,即"("减去")"的数量。如果它在任何时候低于零,或者不以零结束,序列是无效的, 否则它是有效的。
代码
class Solution {
public List<String> generateParenthesis(int n) {
List<String> combinations = new ArrayList();
generateAll(new char[2 * n], 0, combinations);
return combinations;
}
public void generateAll(char[] current, int pos, List<String> result) {
if (pos == current.length) {
if (valid(current))
result.add(new String(current));
} else {
current[pos] = '(';
generateAll(current, pos+1, result);
current[pos] = ')';
generateAll(current, pos+1, result);
}
}
public boolean valid(char[] current) {
int balance = 0;
for (char c: current) {
if (c == '(') balance++;
else balance--;
if (balance < 0) return false;
}
return (balance == 0);
}
}
复杂度解析
- 时间复杂度:
, 因为我们需要创建O(n^2n)和验证序列O(n)。
- 空间复杂度:
。
其实这个方法应该第一个说,但最后才想起来,暴力解法虽然是暴力的但也是解法,虽然不推荐但它也有自身的价值在实际项目中可能有时候真的会临时将就使用它,或者说在它的基础上进行改进,总归是可以参考的思路。
以上内容为做题时候的自行总结,如有不对,感谢指正。