1 什么是回溯法?
回溯法,也叫做回溯搜索法,它是一种搜索的方式。
回溯法是递归的副产品,理解了递归才能理解回溯法。
2 怎么理解回溯法?
本质是穷举法,列举出所有的可能性,选出我们想要的答案,并不是什么高效的算法。
如果想让回溯法高效一点,可以增加一些剪枝的操作,但也改变不了穷尽的本质。
3 回溯法的模板
void backTrace(参数){
if(终止条件){
存放结果;
return;
}
for(遍历数组){
选择节点;
backTrace(参数);
撤销选择的节点;
}
}
4 案例分析
LeetCode77:组合
描述:给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
示例 :
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
普通for循环怎么实现?
当n=4,k=2的时候,可以用for循环暴力破解
public class Main {
public static void main(String[] args) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
list.add(i);
for (int j = i + 1; j <= 4; j++) {
list.add(j);
// 凑满两个,塞进去返回结果
res.add(new ArrayList<>(list));
list.remove(list.size() - 1);
}
list.remove(list.size() - 1);
}
System.out.println(res);
}
}
返回结果:
for暴力破解存在的问题:如果n=100,k=50的情况下,就需要50个for循环。
for(...){
for(...){
// 50个循环
......
}
}
无法用常规手段暴力破解,因此需要回溯法递归完成穷举。
回溯法代码实现:
public class LeetCode77 {
public static void main(String[] args) {
LeetCode77 leetCode77 = new LeetCode77();
System.out.println(leetCode77.combine(4, 2));
}
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> list = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backTrack(n, k, 1);
return res;
}
private void backTrack(int n, int k, int start) {
if (list.size() == k) {
res.add(new ArrayList<>(list));
return;
}
for (int i = start; i <= n; i++) {
list.add(i);
backTrack(n, k, i + 1);
list.remove(list.size() - 1);
}
}
}
执行结果:
5 LeetCode回溯专题相关
(1)LeetCode39:组合总和
描述:给定一个无重复数字的数组 candidates
和目标整数target
,找出candidates
中数字和为target
的所有组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]]
示例 2:
输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]]
代码实现:
public class LeetCode39 {
public static void main(String[] args) {
int[] arr = new int[]{2, 3, 6, 7};
int target = 7;
LeetCode39 leetCode39 = new LeetCode39();
System.out.println(leetCode39.combinationSum(arr, target));
}
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> list = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] nums, int target) {
backTrace(nums, target, 0);
return res;
}
private void backTrace(int[] nums, int target, int start){
if(target < 0){
return;
}
if(target == 0){
res.add(new ArrayList<>(list));
return;
}
for(int i = start; i < nums.length; i++){
list.add(nums[i]);
backTrace(nums, target - nums[i], i);
list.remove(list.size() - 1);
}
}
}
执行结果:
(2)LeetCode40:组合总和II
描述:给定一个无重复数字的数组 candidates
和目标整数target
,找出candidates
中数字和为target
的所有组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]
代码实现:
public class LeetCode40 {
public static void main(String[] args) {
LeetCode40 leetCode40 = new LeetCode40();
System.out.println(leetCode40.combinationSum2(new int[]{10, 1, 2, 7, 6, 1, 5}, 8));
}
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> list = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] nums, int target) {
Arrays.sort(nums);
boolean[] flag = new boolean[nums.length];
backTrace(nums, target, 0, flag);
return res;
}
private void backTrace(int[] nums, int target, int start, boolean[] flag) {
if (target < 0) {
return;
}
if (target == 0) {
res.add(new ArrayList<>(list));
return;
}
for (int i = start; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1] && !flag[i - 1]) {
continue;
}
list.add(nums[i]);
flag[i] = true;
backTrace(nums, target - nums[i], i + 1, flag);
flag[i] = false;
list.remove(list.size() - 1);
}
}
}
执行结果:
(3)LeetCode46:全排列
描述:给定一个不含重复数字的数组 nums
,返回其所有可能的全排列 。
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]]
代码实现:
public class LeetCode46 {
public static void main(String[] args) {
LeetCode46 leetCode46 = new LeetCode46();
System.out.println(leetCode46.permute(new int[]{1, 2, 3}));
}
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> list = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] flag = new boolean[nums.length];
backTrack(nums, flag);
return res;
}
private void backTrack(int[] nums, boolean[] flag) {
if (list.size() == nums.length) {
res.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < nums.length; i++) {
if (flag[i]) {
continue;
}
list.add(nums[i]);
flag[i] = true;
backTrack(nums, flag);
flag[i] = false;
list.remove(list.size() - 1);
}
}
}
执行结果:
(4)LeetCode47:全排列II
描述:给定一个包含重复数字的序列 nums
,按任意顺序返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]
示例 2:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
代码实现:
public class LeetCode47 {
public static void main(String[] args) {
LeetCode47 leetCode47 = new LeetCode47();
System.out.println(leetCode47.permuteUnique(new int[]{1, 1, 2}));
}
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> list = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
boolean[] flag = new boolean[nums.length];
backTrace(nums, flag);
return res;
}
private void backTrace(int[] nums, boolean[] flag) {
if (list.size() == nums.length) {
res.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < nums.length; i++) {
if (flag[i]) {
continue;
}
if (i > 0 && nums[i] == nums[i - 1] && !flag[i - 1]) {
continue;
}
list.add(nums[i]);
flag[i] = true;
backTrace(nums, flag);
flag[i] = false;
list.remove(list.size() - 1);
}
}
}
执行结果: