回溯算法的定义:回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。适用于求解组合数较大的问题。
对于回溯问题,总结出一个递归函数模板,包括以下三点
- 递归函数的开头写好跳出条件,满足条件才将当前结果加入总结果中
- 已经拿过的数不再拿 if(s.contains(num)){continue;}
- 遍历过当前节点后,为了回溯到上一步,要去掉已经加入到结果list中的当前节点。
针对不同的题目采用不同的跳出条件或判断条件。
下面通过几个例子来说明对上述模板的使用
46. Permutations
Given a collection of distinct integers, return all possible permutations.
Example:
Input: [1,2,3] Output: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
backtracking(list, new ArrayList<>(), nums);
return list;
}
private void backtracking(List<List<Integer>> list, List<Integer> tempList, int [] nums){
if(tempList.size() == nums.length){ //已将全部数选出,满足条件加入结果集,结束递归
list.add(new ArrayList<>(tempList));
} else{
for(int i = 0; i < nums.length; i++){
if(tempList.contains(nums[i])) continue; // 已经选过的数不再选
tempList.add(nums[i]); //选择当前节点
backtracking(list, tempList, nums); //递归
tempList.remove(tempList.size() - 1); //回溯到上一步,去掉当前节点
}
}
}
47. Permutations II
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
Example:
Input: [1,1,2] Output: [ [1,1,2], [1,2,1], [2,1,1] ]
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> ret = new ArrayList<>();
Arrays.sort(nums);
backtracking(ret,new ArrayList<>(),nums,new boolean[nums.length]);
return ret;
}
public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] nums,boolean[] used) {
if(list.size()==nums.length) {
ret.add(new ArrayList<>(list));
return;
}
for(int i=0;i<nums.length;i++) {
//重复元素只按顺序选择,若当前元素未被选择且前一元素与当前元素值相等也未被选择则跳过,这一可能情况与先选小序号后选大序号的相同元素相同
&n