回溯法通常用来解决决策和路径问题,使用回溯法来解决的的问题通常返回的结果都是一个集合,在计算的过程中还需要进行剪枝。解决回溯问题实际上就是一个决策树的遍历过程,需要思考下面三个问题:
- 路径:也就是已经做出的选择
- 选择列表:也就是你当前可以做的选择,通常有一个已选择列表和一个可选择列表
- 结束条件:也就是到达决策树的底层,无法再做出选择时(通常会在这里做判断,遍历到当前的路径是否满足条件,如果满足则加入到结果集合里面)
其一般的解题模板如下:
result = []def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择
其实会发现这里和DFS的伪代码很像,但是不同之处在于,DFS通常没有撤销选择那一步,DFS一般是一条道路走到黑就可以了,但回溯法因为会记录路径以及当前的选择或被选择情况,当走到其它同级的决策分支时需要将当前分支的决策选择给重置掉
回溯法典型案例
class Solution {public List<List<Integer>> permute(int[] nums) {ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();if (nums.length <0) {return res;}dfs(nums, new HashSet<Integer>(), new ArrayList<Integer>(), res);return res;}private void dfs(int[] nums, Set<Integer> useSet, List<Integer> path, List<List<Integer>> res) {//dfs的终止条件,就是所有元素都用过了if (useSet.size() == nums.length) {res.add(path);}//dfs的处理逻辑,要将数组中的每个元素都用到for (int i=0; i< nums.length; i++) {//如果没有用过这个元素,则进行dfsif (!useSet.contains(nums[i])) {path.add(nums[i]);useSet.add(nums[i]);//这里要new一个新的出来,避免下面的清理冲突dfs(nums, new HashSet<>(useSet), new ArrayList<>(path), res);//清理资源,为其它的元素的dfsuseSet.remove(nums[i]);path.remove(path.size()-1);}}}}
//这个题目和上一个区别就是元素存在重复的,因此就需要对元素进行排序和判重剪枝class Solution {public List<List<Integer>> permuteUnique(int[] nums) {List<List<Integer>> result = new ArrayList<>();if (nums.length == 0) {return result;}Arrays.sort(nums);boolean[] used = new boolean[nums.length];dfs(nums, used, nums.length, result, new ArrayList<>());return result;}private void dfs(int[] nums, boolean[] used, int depth, List<List<Integer>> res, List<Integer> path) {if (depth == 0) {res.add(path);return;}for (int i=0; i<nums.length; i++) {//相对于无重复的,这里增加对前一个已经遍历并撤销选中的分支进行减枝if (i>0 && nums[i] == nums[i-1] && !used[i-1]) {continue;}if (!used[i]) {path.add(nums[i]);used[i] = true;dfs(nums, used, depth-1, res, new ArrayList<>(path));used[i] = false;path.remove(path.size()-1);}}}}
class Solution {public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();if (candidates.length == 0) {return res;}Arrays.sort(candidates);dfs(candidates,0, target, new ArrayList<>(), res);return res;}private void dfs(int[] candidates,int begin, int target, List<Integer> path, List<List<Integer>> res) {if (target == 0) {res.add(path);return;}if (target < candidates[0]) {return;}for (int i=begin; i< candidates.length; i++) {path.add(candidates[i]);//当选了i之后,后面的元素就只从i开始,避免返回去又重复dfs(candidates, i, target-candidates[i], new ArrayList<>(path), res);path.remove(path.size()-1);}}}
class Solution {public List<List<Integer>> combinationSum2(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();if (candidates.length == 0) {return res;}Arrays.sort(candidates);dfs(candidates,0, target, new ArrayList<>(), res);return res;}private void dfs(int[] candidates,int begin, int target, List<Integer> path, List<List<Integer>> res) {if (target == 0) {res.add(path);return;}for (int i=begin; i< candidates.length; i++) {//剩下的元素都比target大,直接剪枝if (target < candidates[begin]) {return;}//去除重复的元素,进行剪枝if (i> begin && candidates[i] == candidates[i-1]) {continue;}path.add(candidates[i]);//当选了i之后,后面的元素就只从i+1开始,避免i位置的元素重复使用dfs(candidates, i+1, target-candidates[i], new ArrayList<>(path), res);path.remove(path.size()-1);}}}
