回溯算法的定义:回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。适用于求解组合数较大的问题。
对于回溯问题,总结出一个递归函数模板,包括以下三点
- 递归函数的开头写好跳出条件,满足条件才将当前结果加入总结果中
- 已经拿过的数不再拿 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++) { //重复元素只按顺序选择,若当前元素未被选择且前一元素与当前元素值相等也未被选择则跳过,这一可能情况与先选小序号后选大序号的相同元素相同 if(used[i] || i>0 && nums[i]==nums[i-1] && !used[i-1]) continue; used[i]=true; list.add(nums[i]); //选择当前点 backtracking(ret,list,nums,used); //递归 used[i]=false; list.remove(list.size()-1); //回溯到上一步,去掉当前节点 } }
39. Combination Sum
Given a set of candidate numbers (candidates
) (without duplicates) and a target number (target
), find all unique combinations in candidates
where the candidate numbers sums to target
.
The same repeated number may be chosen from candidates
unlimited number of times.
Note:
- All numbers (including
target
) will be positive integers. - The solution set must not contain duplicate combinations.
Example 1:
Input: candidates =[2,3,6,7],
target =7
, A solution set is: [ [7], [2,2,3] ]
Example 2:
Input: candidates = [2,3,5],
target = 8,
A solution set is:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> ret = new ArrayList<>(); backtracking(ret,new ArrayList<>(),candidates,target); return ret; } public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] candidates,int target) { if(target<0) return; else if(target==0) ret.add(new ArrayList<>(list)); else { for(int i=0;i<candidates.length;i++) { list.add(candidates[i]); backtracking(ret,list,candidates,target-candidates[i]); list.remove(list.size()-1); } } }
Your answer
[[2,2,3],[2,3,2],[3,2,2],[7]]
Expected answer
[[2,2,3],[7]]
public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> ret = new ArrayList<>(); backtracking(ret,new ArrayList<>(),candidates,target,0); return ret; } public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] candidates,int target,int position) { if(target<0) return; else if(target==0) ret.add(new ArrayList<>(list)); else { for(int i=position;i<candidates.length;i++) { list.add(candidates[i]); backtracking(ret,list,candidates,target-candidates[i],i); list.remove(list.size()-1); } } }
40. Combination Sum II
Given a collection of candidate numbers (candidates
) and a target number (target
), find all unique combinations in candidates
where the candidate numbers sums to target
.
Each number in candidates
may only be used once in the combination.
Note:
- All numbers (including
target
) will be positive integers. - The solution set must not contain duplicate combinations.
Example 1:
Input: candidates =[10,1,2,7,6,1,5]
, target =8
, A solution set is: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]
Example 2:
Input: candidates = [2,5,2,1,2], target = 5, A solution set is: [ [1,2,2], [5] ]
public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> ret = new ArrayList<>(); Arrays.sort(candidates); backtracking(ret,new ArrayList<>(),candidates,target,0); return ret; } public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] candidates,int target,int position) { if(target<0) return; else if(target==0) ret.add(new ArrayList<>(list)); else { for(int i=position;i<candidates.length;i++) { if(i>position&&candidates[i]==candidates[i-1]) continue; //当i位置与前一位置数字相同,再次选择i会导致重复答案 list.add(candidates[i]); backtracking(ret,list,candidates,target-candidates[i],i+1); //从当前位的下一位开始 list.remove(list.size()-1); } } }
216. Combination Sum III
Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.
Note:
- All numbers will be positive integers.
- The solution set must not contain duplicate combinations.
Example 1:
Input: k = 3, n = 7 Output: [[1,2,4]]
Example 2:
Input: k = 3, n = 9 Output: [[1,2,6], [1,3,5], [2,3,4]]
public List<List<Integer>> combinationSum3(int k, int n) { List<List<Integer>> ret = new ArrayList<>(); backtracking(ret,new ArrayList<>(),k,n,1); return ret; } public void backtracking(List<List<Integer>> ret,List<Integer> list,int k,int n,int position) { if(n<0||list.size()>k) return; else if(n==0&&list.size()==k) ret.add(new ArrayList<>(list)); else { for(int i=position;i<10;i++) { list.add(i); backtracking(ret,list,k,n-i,i+1); list.remove(list.size()-1); } } }
78. Subsets
Given a set of distinct integers, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Input: nums = [1,2,3] Output: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
public List<List<Integer>> subsets(int[] nums) { List<List<Integer>> ret = new ArrayList<>(); backtracking(ret,new ArrayList<>(),nums,0); return ret; } public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] nums,int position) { ret.add(new ArrayList<>(list)); //每次递归将其加入结果集 for(int i=position;i<nums.length;i++) { list.add(nums[i]); backtracking(ret,list,nums,i+1); //从当前位置的下一位置开始选择 list.remove(list.size()-1); } }
90. Subsets II
Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Input: [1,2,2] Output: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
public List<List<Integer>> subsetsWithDup(int[] nums) { List<List<Integer>> ret = new ArrayList<>(); Arrays.sort(nums); backtracking(ret,new ArrayList<>(),nums,0); return ret; } public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] nums,int position) { ret.add(new ArrayList<>(list)); for(int i=position;i<nums.length;i++) { if(i>position&&nums[i]==nums[i-1]) continue; list.add(nums[i]); backtracking(ret,list,nums,i+1); list.remove(list.size()-1); } }以及经典的n皇后问题,在任意一个皇后所在位置的水平、竖直、45度斜线上不能出现皇后的棋子。n皇后放置完毕后绘制棋盘。
if(legal(pos,i,j)) { //判断皇后是否合法 pos[i][j]=true; backtracking(ret,pos,n-1,i+1,0); //递归 pos[i][j]=false; //回溯 }该文从以下两链接处获得思路,可供参考