题目
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
回溯套路:
搜索算法:深度搜索树,列出所有情况,
每一层都有m种选择
思想是专门选择某个元素,列下一个,再固定,等固定该元素所所有情况都列完了,再返回上一层列举另一个元素。这样遍历整个树。
回溯:就是保存一些状态变量,状态变量满足某些情况下才去列举元素,否则跳过这个选择。这样情况就会少一些。 不是暴力的列出所有元素组合。
思路
暴力法
先枚举出所有情况,再筛选出满足的。
枚举出所有情况:
筛选出满足的:
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);
}
}
回溯法
我们只在在序列仍然有效的时候才添加’(’ or ‘)’ ,而不是像暴力法不管前面的,列举所有情况。
方法:跟踪到目前为止放置的左括号和右括号的数目来做到这一点。记录前面的状态。
如果左括号数量不大于n, 我们可以放一个左括号,如果右括号数量小于左括号,可以放一个右括号。
全排列也是一样,不是每个dfs都循环(i = 0; i < n; i++) ,而是当数字i used[i]访问过了,就不会再往下找了,固定前面的,枚举,既不会漏也不会重。
生成括号树,由于先生成(后生成)的() 和 先生成)后生成(的() 没有区别,所以减掉当left < right这一种情况。剪枝
import java.util.ArrayList;
import java.util.List;
public class leetcode_22_generateParenthesis_huisu_2 {
public static List<String> generateParenthesis(int n) {
// StringBuilder curStr
// 终止条件 curStr.length == 2*n
// 搜索 当满足条件时,添加括号,继续向下找
// 满足条件: 当左括号数量小于n时,可以添加左括号
// 当右括号数量小于左括号数量时,可以添加右括号
if (n == 0) return new ArrayList<>();
List<String> ans = new ArrayList<>();
StringBuilder curStr = new StringBuilder();
dfs(curStr, ans, 0, 0, n);
return ans;
}
public static void dfs(StringBuilder curStr, List<String> ans,
int left, int right, int max) {
if (curStr.length() == 2 * max) {
ans.add(curStr.toString());
return;
}
// 剪枝
if(left < right) {
return;
}
if(left < max) {
curStr.append('(');
dfs(curStr, ans, left + 1, right, max);
curStr.deleteCharAt(curStr.length() - 1);
}
if(right < left) {
curStr.append(')');
dfs(curStr, ans, left, right + 1, max);
curStr.deleteCharAt(curStr.length() - 1);
}
}
public static void main(String[] args) {
List<String> res = generateParenthesis(2);
System.out.println(res);
}
}
广度优先搜索
通过编写广度优先遍历的代码,读者可以体会一下,为什么搜索几乎都是用深度优先遍历(回溯算法)。
广度优先遍历,得程序员自己编写结点类,显示使用队列这个数据结构。深度优先遍历的时候,就可以直接使用系统栈,在递归方法执行完成的时候,系统栈顶就把我们所需要的状态信息直接弹出,而无须编写结点类和显示使用栈。
下面的代码,读者可以把 Queue 换成 Stack,提交以后,也可以得到 Accept。
读者可以通过比较:
1、广度优先遍历;
2、自己使用栈编写深度优先遍历;
3、使用系统栈的深度优先遍历(回溯算法)。
来理解 “回溯算法” 作为一种 “搜索算法” 的合理性。
还是上面的题解配图(1),使用广度优先遍历,结果集都在最后一层,即叶子结点处得到所有的结果集,编写代码如下。
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Solution {
class Node {
/**
* 当前得到的字符串
*/
private String res;
/**
* 剩余左括号数量
*/
private int left;
/**
* 剩余右括号数量
*/
private int right;
public Node(String str, int left, int right) {
this.res = str;
this.left = left;
this.right = right;
}
}
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
if (n == 0) {
return res;
}
Queue<Node> queue = new LinkedList<>();
queue.offer(new Node("", n, n));
while (!queue.isEmpty()) {
Node curNode = queue.poll();
if (curNode.left == 0 && curNode.right == 0) {
res.add(curNode.res);
}
if (curNode.left > 0) {
queue.offer(new Node(curNode.res + "(", curNode.left - 1, curNode.right));
}
if (curNode.right > 0 && curNode.left < curNode.right) {
queue.offer(new Node(curNode.res + ")", curNode.left, curNode.right - 1));
}
}
return res;
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/