伪代码:
void backtracking(参数){
if(终止条件){
存放结果;
return;
}
for(选择:本层集合中的元素 ( 树中节点孩子的数量就是集合的大小 ) ){
处理节点;
backtracking(路径,选择列表);
回溯,撤销处理的结果。
}
}
组合问题
77.组合:
class Solution {
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return res;
}
private void backtracking(int n , int k , int startIndex){
if(path.size() == k){
res.add(new ArrayList(path));
return;
}
for(int i = startIndex ; i <= n ; i++){
path.add(i);
backtracking(n,k,i+1);
path.remove(path.size() - 1);
}
}
}
216. 组合总和 III:
class Solution {
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtracking(k,n,0,1);
return res;
}
private void backtracking(int k , int n , int sum , int startIndex){
if(sum > n){
return;
}
if(path.size() == k){
if(sum == n){
res.add(new ArrayList<>(path));
}
return;
}
for(int i = startIndex ; i <= 9 ; i++){
sum += i;
path.add(i);
backtracking(k,n,sum,i+1);
sum -= i;
path.remove(path.size()-1);
}
}
}
17. 电话号码的字母组合
class Solution {
private String[] letterMap = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
private List<String> res = new ArrayList<>();
private StringBuilder path = new StringBuilder();
public List<String> letterCombinations(String digits) {
if(digits.length() == 0) return res;
backtracking(digits,0);
return res;
}
private void backtracking(String digits , int index){
if(index == digits.length()){
res.add(path.toString());
return;
}
int digit = digits.charAt(index) - '0';
String letters = letterMap[digit];
for(int i = 0 ; i < letters.length() ; i++){
path.append(letters.charAt(i));
backtracking(digits,index+1);
path.deleteCharAt(path.length()-1);
}
}
}
39. 组合总和
class Solution {
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
private int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
dfs(candidates,target,0);
return res;
}
private void dfs(int[] candidates , int target , int startIndex){
if(sum > target) return;
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i = startIndex ; i < candidates.length ; i++){
path.add(candidates[i]);
sum += candidates[i];
dfs(candidates,target,i);
sum -= candidates[i];
path.remove(path.size()-1);
}
}
}
40.组合总和II
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
private int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
dfs(candidates,target,0);
return res;
}
private void dfs(int[] candidates , int target , int startIndex){
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i = startIndex ; i < candidates.length ; i++){
//去重
if(i > startIndex && candidates[i] == candidates[i-1]) continue;
path.add(candidates[i]);
sum += candidates[i];
dfs(candidates,target,i+1);//元素只能使用一次i+1
sum -= candidates[i];
path.remove(path.size()-1);
}
}
}
131.分割回文串
93.复原IP地址
子集问题
子集问题是找树的所有节点!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!
有同学问了,什么时候for可以从0开始呢?
求排列问题的时候,就要从0开始,因为集合是有序的,{1, 2} 和{2, 1}是两个集合。
78.子集
class Solution {
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums,0);
return res;
}
private void backtracking(int[] nums , int startIndex){
res.add(new ArrayList<>(path));
if(startIndex >= nums.length){
return;
}
for(int i = startIndex ; i < nums.length ; i++){
path.add(nums[i]);
backtracking(nums,i+1);
path.remove(path.size()-1);
}
}
}
90.子集II
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
backtracking(nums,0);
return res;
}
private void backtracking(int[] nums , int startIndex){
res.add(new ArrayList<>(path));
if(startIndex >= nums.length) return;
for(int i = startIndex ; i < nums.length ; i++){
if(i > startIndex && nums[i] == nums[i-1]) continue;
path.add(nums[i]);
backtracking(nums,i+1);
path.remove(path.size()-1);
}
}
}
全排列问题
可以看出叶子节点,就是收割结果的地方。
那么什么时候,算是到达叶子节点呢?
当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。
46. 全排列
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used = new boolean[nums.length];
backtracking(nums,used);
return res;
}
private void backtracking(int[] nums , boolean[] used){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i = 0 ; i < nums.length ; i++){
if(used[i]) continue;
used[i] = true;
path.add(nums[i]);
backtracking(nums,used);
used[i] = false;
path.remove(path.size()-1);
}
}
}
47. 全排列 II
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];
backtracking(nums,used);
return res;
}
private void backtracking(int[] nums , boolean[] used){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i = 0 ; i < nums.length ; i++){
if(used[i]) continue;
if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false) continue;
used[i] = true;
path.add(nums[i]);
backtracking(nums,used);
used[i] = false;
path.remove(path.size()-1);
}
}
}