22.数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:
输入:n = 1
输出:[“()”]
- 观察会发现,有效的组合在生成时一定满足左括号多余等于右括号,否则比如
())
无论之后怎么加括号最后都是无效组合,生成的过程不难想到用回溯法,在每一位尝试放左括号或者右括号,在前一步的基础上继续各自递归尝试,很像二叉树遍历,基本上套用回溯模版即可 - 回溯模板
-
private void backtrack("原始参数") {//终止条件(递归必须要有终止条件)if ("终止条件") {//一些逻辑操作(可有可无,视情况而定)return;}for (int i = "for循环开始的参数"; i < "for循环结束的参数"; i++) {//一些逻辑操作(可有可无,视情况而定)//做出选择//递归backtrack("新的参数");//一些逻辑操作(可有可无,视情况而定)//撤销选择}}
- 最终代码
-
List<String> res = new ArrayList<>();public List<String> generateParenthesis(int n) {dfs(n, n, "");return res;}// left:能放的左括号数量,right 同理// str:当前生成的字符串public void dfs(int left, int right, String str){// 说明此时右括号数量多余左括号,直接舍弃if(left>right)return;// 左右括号都用完就记录该组合if(left==0 && right == 0){res.add(str);return;}// 左括号用完就继续加右括号,以下同理if(left==0)dfs(left,right-1,str+")");else if(right==0)dfs(left-1,right,str+"(");else{dfs(left-1,right,str+"(");dfs(left,right-1,str+")");}}
- 他人题解:思路一致,代码更精简。实际上只要左括号还有,并且左括号数量大于等于左括号数量,就说明 right>=left>=0,那就可以直接递归,所以可以不分情况
-
public void dfs(int left, int right, String str){// 左括号都用到负数了肯定不行了// 右括号更多必定无效组合// 并且如果通过了以下两个条件,就说明 right 也 >=0,所以不需要再判断 rightif(left<0 || left>right)return;if(left==0 && right == 0){res.add(str);return;}dfs(left-1,right,str+"(");dfs(left,right-1,str+")");}
- 他人题解2:动态规划,从感觉上来说是感觉可以 dp 的,但是这个公式真不太好想。
- 设 dp[i] 表示 n 等于 i 时生成的有效括号组合,递推公式为:
dp[i] = "(" + dp[m] + ")" + dp[k], m + k = i-1
。具体写的时候,这里比较绕的就是 dp[i] 是一个 list,比如dp[1] = ["()"]
,dp[2]=["()()", "(())"]
,所以即使知道知道动态规划方程, dp 部分还是比较难理解 dp[i] = "(" + dp[m] + ")" + dp[k]
的理解:这里的意思其实是 dp[m] 中每个组合和 dp[k] 中每个组合,结合成新的结果。比如"(" + dp[m] + ")" + dp[k]
先简化理解成dp[m]+dp[k]
,假设dp[m]=[1,2], dp[k]=[3,4]
,那么其实得到结果应该为[13,14,23,24]
。 所以核心部分如下:-
for (int m = 0; m < i; m++) {int k = i - 1 - m;List<String> str1 = dp[m];List<String> str2 = dp[k];for (String s1 : str1) {for (String s2 : str2) {cur.add("(" + s1 + ")" + s2);}}}
- 边界条件为
dp[0]= ""
,所以最终代码如下 -
public List<String> generateParenthesis(int n) {// 初始化 [[""]],不然 dp[i-1] 都没东西List<String>[] dp = new List[n + 1];List<String> dp0 = new ArrayList<>();dp0.add("");dp[0] = dp0;for (int i = 1; i <= n; i++) {// 记录 dp[i] 的组合List<String> cur = new ArrayList<>();for (int m = 0; m < i; m++) {int k = i - 1 - m;List<String> str1 = dp[m];List<String> str2 = dp[k];for (String s1 : str1) {for (String s2 : str2) {cur.add("(" + s1 + ")" + s2);}}}dp[i] = cur;}return dp[n];}
- 他人题解2优化:上面 dp 的核心就在于 dp[m] 和 dp [k] 的组合,但是 dp[m] 其实不就是 generateParenthesis(m),所以可以优化如下
-
public static List<String> generateParenthesis(int n) {List<String> list = new ArrayList<>();if (n == 0) {//边界条件的判断list.add("");return list;}for (int m = 0; m < n; m++) {int k = n - m - 1;List<String> first = generateParenthesis(m);List<String> second = generateParenthesis(k);for (String left : first) {for (String right : second) {list.add("(" + left + ")" + right);}}}return list;}