思路
DFS
- 每层供选的元素是剩余的括号,终止条件为剩余0个括号
- 控制括号生成顺序有效:
)
剩余数量必须大于等于(
剩余数量
回溯与不回溯的区别
「回溯算法」强调了在状态空间特别大的时候,只用一份状态变量去搜索所有可能的状态,在搜索到符合条件的解的时候,通常会做一个拷贝,这就是为什么经常在递归终止条件的时候,有 res.add(new ArrayList<>(path)); 这样的代码。正是因为全程使用一份状态变量,因此它就有「恢复现场」和「撤销选择」的需要。
每次搜索的的过程,也可以直接复制一个String,见方法二。
方法一:DFS + 回溯
全程使用一份状态变量SB path 来记录路径。
- 需要回溯
- 添加到res时需要new一下 新写法
class Solution {
List<String> path = new ArrayList<>();
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
dfs(n, n);
return res;
}
// leftNum为当前左括号剩余数量,rightNum为右括号剩余数量
public void dfs(int leftNum, int rightNum) {
if (leftNum == 0 && rightNum == 0) {//左右括号都用完了,找到了正确的排列
String tmp = "";
for (int i = 0; i < path.size(); i++) {
tmp += path.get(i);
}
res.add(tmp);
}
if (leftNum > rightNum) {// "())", ")"
return;
}
if (leftNum != 0) {
path.add("(");
dfs(leftNum - 1, rightNum);
path.remove(path.size() - 1);
}
if (rightNum != 0) {
path.add(")");
dfs(leftNum, rightNum - 1);
path.remove(path.size() - 1);
}
}
}
第一次写法
public class Solution {
StringBuilder path = new StringBuilder();
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n == 0) {
return res;
}
dfs(n, n);
return res;
}
//left: 剩余的"("个数,right:剩余的")"个数
public void dfs(int left, int right) {
if (left == 0 && right == 0) {//剩余括号为0,结束
res.add(path.toString());//SB的toStr相当于new了一个
return;
}
if (left > right) {//剪枝,为了保证顺序有效
return;
}
if (left > 0) {//当"("还有剩余时
path.append("(");
dfs(left - 1, right);
path.deleteCharAt(path.length() - 1);//回溯
}
if (right > 0) {//当")"还有剩余时
path.append(")");
dfs(left, right - 1);
path.deleteCharAt(path.length() - 1);//回溯
}
}
}
方法二:DFS 不回溯
每次搜索都使用新的String变量来记录路径,+
生成了新的字符串,每次往下面传递的时候,都是新字符串。因此在搜索的时候不用回溯。
- 无需回溯
- 直接添加到res中。
public class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n == 0) {
return res;
}
dfs("", n, n);//用string来记录路径
return res;
}
public void dfs(String curStr, int left, int right) {
// 因为每一次尝试,都使用新的字符串变量而不是引用,所以无需回溯
// 在递归终止的时候,直接把它添加到结果集即可。
if (left == 0 && right == 0) {
res.add(curStr);
return;
}
// 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
if (left > right) {
return;
}
if (left > 0) {
dfs(curStr + "(", left - 1, right);
}
if (right > 0) {
dfs(curStr + ")", left, right - 1);
}
}
}
方法三 :找位置插入,去重。
太憨了,别这么写
class Solution {
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
result.add("()");
if (n == 1) {
return result;
}
for (int i = 2; i <= n; i++) {
List<String> tmp = new ArrayList<>();
for (int j = 0; j < result.size(); j++ ) {
StringBuilder sb = new StringBuilder(result.get(j));
for (int k = 0; k < sb.length(); k++) {
sb.insert(k, "()");
tmp.add(sb.toString());
sb = new StringBuilder(result.get(j));
}
}
result = tmp.stream().distinct().collect(Collectors.toList());
}
Collections.sort(result);
return result;
}
}