回溯算法本质就是枚举,在给定的枚举集合中不断从其中尝试搜索找到问题的解,若是在搜索过程当中发现不知足求解条件,则回溯返回,尝试其余路径继续搜索解决,这种走不通就回退再尝试其余路径的方法就是回溯法。算法
解决一个回溯问题,实际上就是一个决策树的遍历过程。你只须要思考3个问题:code
通用解决方案伪代码blog
result = [] function backtrack(路径, 选择列表) { if 知足结束条件: result.add(路径) return for 选择 in 选择列表: 作选择 backtrack(路径, 选择列表) 撤销选择 }
它通常是解决树形问题的,问题分解成多个阶段,每一个阶段有多个解,这个就构成了一颗树,因此判断问题是否能够用回溯算法的关键在于它是否能够转成一个树形问题。
另外咱们也发现若是可以缩小每一个阶段的可选解,就能让问题的搜索规模都缩小,这种叫作剪枝,经过剪枝能有效下降整个问题的搜索复杂度。rem
咱们在高中的时候就作过排列组合的数学题,咱们也知道 n 个不重复的数,全排列共有 n! 个。
那么咱们当时是怎么穷举全排列的呢?比方说给三个数 [1,2,3],你确定不会无规律地乱穷举,通常是这样:get
先固定第一位为 1,而后第二位能够是 2,那么第三位只能是 3;而后能够把第二位变成 3,第三位就只能是 2 了;而后就只能变化第一位,变成 2,而后再穷举后两位……数学
其实这就是回溯算法,咱们高中无师自通就会用,或者有的同窗直接画出以下这棵回溯树:
io
public class Test { public static void main(String[] args) { Test test = new Test(); int[] nums = {1, 2, 3}; System.out.println(test.permute(nums)); } public List<List<Integer>> permute(int[] nums) { if (nums == null || nums.length == 0) { return Collections.emptyList(); } List<List<Integer>> result = new ArrayList<>(); backtrack(result, new ArrayList<>(), nums); return result; } private void backtrack(List<List<Integer>> result, List<Integer> selectNums, int[] allNums) { if (selectNums.size() == allNums.length) { result.add(new ArrayList<>(selectNums)); return; } for (Integer num : allNums) { // 剪枝 if (selectNums.contains(num)) { continue; } selectNums.add(num); backtrack(result, selectNums, allNums); selectNums.remove(num); } } }
参考资料function