回溯是一种比较特殊的递归,这种递归有以下两个特点:
1、程序处于尝试搜索的状态,选择条件向某个分支执行,当不满足情况时,回溯到先前的分叉点
2、到达某一终止条件时,记录数据并返回
回溯题的常见模板伪代码:
def backTrack(路径, 可选列表) :
if 路径长度 == 可选列表长度或满足其他条件:
记录该路径
for 可选项 in 可选列表 :
添加可选项(另外根据情况判断是否需要将该可选项进行剔除)
backTrack(路径, 可选列表)
删除可选项(另外根据情况判断是否需要将该可选项进行回添)
全排列问题
- 全排列
全排列的主要目的是取出一个数后就不再放入,那么可以通过visited数组来表示哪个数据是否访问过,进而再取那些并么有被访问过的数据
class Solution {
List<List<Integer>> list;
public List<List<Integer>> permute(int[] nums) {
list = new ArrayList<>();
boolean[] visited = new boolean[nums.length];
trackBack(new ArrayList<>(), nums, visited);
return list;
}
public void trackBack(List<Integer> list1, int[] nums, boolean[] visited) {
if (list1.size() == nums.length) {
list.add(new ArrayList<>(list1));
return;
}
for (int i = 0; i < nums.length; i++) {
if (visited[i]) {
continue;
}
visited[i] = true;
list1.add(nums[i]);
trackBack(list1, nums, visited);
list1.remove(list1.size() - 1);
visited[i] = false;
}
}
}
- 全排列II
对于全排列2而言,主要增加难度在于如何处理数组中的重复数据,主要的方式是通过数组排序并判定相邻数据中是否相同。如果相同并且该数据先前是否已经访问过,则跳过;否则对其进行遍历;**
class Solution {
List<List<Integer>> list;
public List<List<Integer>> permuteUnique(int[] nums) {
list = new ArrayList<>();
boolean[] visited = new boolean[nums.length];
List<Integer> list1 = new ArrayList<>();
if(nums.length == 0)
return list;
Arrays.sort(nums);
dfs(list1, nums, visited);
return list;
}
public void dfs(List<Integer> list1, int[] nums, boolean[] visited){
//设置终止条件:
if(list1.size() == nums.length) {
list.add(new ArrayList<>(list1));
return;
}
for(int i = 0; i < nums.length; i++) {
if (!visited[i]) {
//相比于第一类全排列,最关键的判定条件:
//首先需要将输入数组进行排序,之后判断,如果位置i上的数等于位置i-1上的数,
//并且visited[i-1]=false说明这个数在上一个排列中已经在这个位置放置过了,
//那么说明该位置不需要在放置重复的数
if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1])
continue;
visited[i] = true;
list1.add(nums[i]);
dfs(list1, nums, visited);
visited[i] = false;
list1.remove(list1.size() - 1);
}
}
}
}
组合总和问题
- 数组总和
直接套用回溯公式即可解决:
class Solution {
List<List<Integer>> list;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
list = new ArrayList<>();
trackBack(new ArrayList<>(), candidates, target, 0);
return list;
}
public void trackBack(List<Integer> list1, int[] candidates, int target, int index) {
if (target == 0) {
list.add(new ArrayList<>(list1));
return;
}
for (int i = index; i < candidates.length; i++) {
if (target < 0) {
break;
}
list1.add(candidates[i]);
trackBack(list1, candidates, target - candidates[i], i);
list1.remove(list1.size() - 1);
}
}
}
- 组合总和II
该题关键在于如何处理去重复的问题,一般通过对整个序列进行排序并且在对序列遍历时略过相等部分
class Solution {
List<List<Integer>> list;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
list = new ArrayList<>();
Arrays.sort(candidates);
trackBack(new ArrayList<>(), candidates, target, 0);
return list;
}
public void trackBack(List<Integer> list1, int[] candidates, int target, int index) {
if (target == 0) {
list.add(new ArrayList<>(list1));
return;
}
for (int i = index; i < candidates.length; i++) {
if (target < 0) {
break;
}
if (i > index && candidates[i] == candidates[i - 1]) {
continue;
}
list1.add(candidates[i]);
trackBack(list1, candidates, target - candidates[i], i + 1);
list1.remove(list1.size() - 1);
}
}
}