回溯(backtrack)简介
1. 什么是回溯法
回溯(backtrack)是按条件沿着树的深度去搜索,直到达到满足目标条件或不再可能达到目标,就回退一步树的深度,选择树的另一个分支,这种走不通就退回的换分支技术为回溯。
回溯的本质是一个穷举的深度优先搜索(DFS),每一种深搜都会对应一棵树,其时间复杂度较高,与穷举的区别在于满足条件或者知道后面不可能满足条件后,就进行回退,寻找另一种可能的解。其降低了重复的穷举的步骤,时间复杂度比穷举低。
2. 什么情况需要回溯
当只需要最优解时与最终解时,很容易想到贪心与动态规划,但是一旦要给出所有可能的解,这时就应该使用回溯了。
3. 回溯模板(java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public List<List<Integer>> getResult(int[] nums) { List<List<Integer>> resList = new ArrayList<>(); Deque<Integer> tempDeque = new ArrayDeque; backTrack(nums, 0, resList, tempDeque, ?1, ?2); return resList; }
public void backTrack(int[] nums, int deepth, List<List<Integer>> resList, Deque<Integer> tempDeque) { if (到达终点/达到条件) { resList.add(new ArrayList<Integer>(temList)); return; } if (越界或者不合法状态) { return; } if (特殊状态) { return; } for (扩展方式) { if (扩展方式达到合法状态) { 修改操作; 标记使用; backTrack(nums, deepth + 1, resList, tempDeque, ?1, ?2); 还原标记; tempList.pollLast(); } } }
|
4. 名词释义
- 回溯:搜索到结果后,撤销上一步的搜索,进行另一个可能结果的下一步搜索
- 剪枝:在不可继续搜索的情况下,绝对不可能满足目标情况下,提前return,触发递归父级的回溯,从而提前回溯。
- 记忆集:有时结果不需要重复的,这时可以用set存储结果,或者boolean数组标记对应的下标是否被使用,避免重复使用。
- 排序:由于回溯本质是一个优化过后的穷举,其时间复杂度较高,因此在处理之前,可以考虑对其进行O(NlogN)时间复杂度排序,由于时间复杂度只取最高的,因此该时间复杂度可以忽略不记。
5. 例子
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
例:输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class Solution { public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<Integer> tempList = new ArrayList<>(); List<List<Integer>> resList = new ArrayList<>(); Arrays.sort(candidates); backTrack(candidates, tempList, resList, target, 0); return resList; }
public void backTrack(int[] candidates, List<Integer> tempList, List<List<Integer>> resList, int target, int idx) { if (target == 0) { resList.add(new ArrayList<Integer>(tempList)); return; } if (idx == candidates.length) { return; } Set<Integer> tempSet = new HashSet<>(); for (int i = idx; i < candidates.length; i++) { if (candidates[i] > target) { break; } if (tempSet.contains(candidates[i])) { continue; } tempList.add(candidates[i]); tempSet.add(candidates[i]); backTrack(candidates, tempList, resList, target - candidates[i], i + 1); tempList.remove(tempList.size()-1); } } }
|
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
例:输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> resList = new ArrayList<>(); Deque<Integer> tempList = new ArrayDeque<>(); boolean[] used = new boolean[nums.length]; if (nums.length == 0) { return resList; } backTrack(nums, 0, resList, tempList, used); return resList; }
public void backTrack(int[] nums, int idx, List<List<Integer>> resList, Deque<Integer> tempList, boolean[] used) { if (idx == nums.length) { resList.add(new ArrayList<Integer>(tempList)); return; } for (int i = 0; i < nums.length; i++) { if (!used[i]) { tempList.addLast(nums[i]); used[i] = true; backTrack(nums, idx + 1, resList, tempList, used); used[i] = false; tempList.pollLast(); } } } }
|