回溯算法和深度优先遍历在求全部情况的时候尤其适用,例如力扣22题
这一类问题中,一般都是在一颗隐形的树上进行求解,可以使用深度优先遍历或者广度优先遍历,但是我们自己写代码推荐使用深度优先遍历,深度优先遍历可以使用递归,借助系统栈完成状态的转移。
先给出第一种非全局变量不需要显示的撤销操作:
import java.util.ArrayList;
import java.util.List;
public class Solution {
// 做减法
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
// 特判
if (n == 0) {
return res;
}
// 执行深度优先遍历,搜索可能的结果
dfs("", n, n, res);
return res;
}
/**
* @param curStr 当前递归得到的结果
* @param left 左括号还有几个可以使用
* @param right 右括号还有几个可以使用
* @param res 结果集
*/
private void dfs(String curStr, int left, int right, List<String> res) {
// 因为每一次尝试,都使用新的字符串变量,所以无需回溯
// 在递归终止的时候,直接把它添加到结果集即可,注意与「力扣」第 46 题、第 39 题区分
if (left == 0 && right == 0) {
res.add(curStr);
return;
}
// 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
if (left > right) {
return;
}
if (left > 0) {
dfs(curStr + "(", left - 1, right, res);
}
if (right > 0) {
dfs(curStr + ")", left, right - 1, res);
}
}
}
此处采用的是局部变量,每一次使用dfs函数进行递归的时候,每一次都是分配的新的地址,因此最后加入res的时候,只需要直接加入就可以,跟完整的回溯算法还是存在一定差别。
下方的代码为完整回溯法解决本题的方法,针对于完整的回溯而言,首先存在一个全局变量,全局变量每次都负责保存当前的数值,之后负责将当前的数值存放入res中,最后返回结果。由于全局只有这一个变量,使得加入新的字符之后会存在一个回溯的过程,因此在主逻辑算法的方法中存在deleteChart方法,用于删除最后一个字符,进行回溯的操作。
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
if (n == 0) {
return res;
}
StringBuilder path = new StringBuilder();
dfs(path, n, n, res);
return res;
}
/**
* @param path 从根结点到任意结点的路径,全程只使用一份
* @param left 左括号还有几个可以使用
* @param right 右括号还有几个可以使用
* @param res
*/
private void dfs(StringBuilder path, int left, int right, List<String> res) {
if (left == 0 && right == 0) {
// path.toString() 生成了一个新的字符串,相当于做了一次拷贝,这里的做法等同于「力扣」第 46 题、第 39 题
res.add(path.toString());
return;
}
// 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
if (left > right) {
return;
}
if (left > 0) {
path.append("(");
dfs(path, left - 1, right, res);
path.deleteCharAt(path.length() - 1);
}
if (right > 0) {
path.append(")");
dfs(path, left, right - 1, res);
path.deleteCharAt(path.length() - 1);
}
}
}
类似题目可以参考力扣46题全排列
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<List<Integer>> permute(int[] nums) {
// 首先是特判
int len = nums.length;
// 使用一个动态数组保存所有可能的全排列
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
boolean[] used = new boolean[len];
List<Integer> path = new ArrayList<>();
dfs(nums, len, 0, path, used, res);
return res;
}
private void dfs(int[] nums, int len, int depth,
List<Integer> path, boolean[] used,
List<List<Integer>> res) {
if (depth == len) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < len; i++) {
if (!used[i]) {
path.add(nums[i]);
used[i] = true;
dfs(nums, len, depth + 1, path, used, res);
// 注意:这里是状态重置,是从深层结点回到浅层结点的过程,代码在形式上和递归之前是对称的
used[i] = false;
path.remove(path.size() - 1);
}
}
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
Solution solution = new Solution();
List<List<Integer>> lists = solution.permute(nums);
System.out.println(lists);
}
}