前言

今天是学习回溯算法的第四天,我们继续来一起学习回溯算法蕴含的逻辑处理,希望博主记录的内容能够对大家有所帮助 ,一起加油吧朋友们!💪💪💪

非递减子序列

LeetCode题目链接

这道题就是给一个整数数组,然后要返回数组里所有不同的递增子序列,要体现递增,子序列长度大于1

1728955152502

我们来思路梳理

我们进一步来梳理回溯三要素

1
2
3
4
5
6
7
//nums: 输入的整数数组
//startIndex: 当前递归处理到的数组起始位置
//path: 当前递增子序列(构建中的路径)
//result: 存储所有不同的递增子序列的列表
//used: 用于记录在当前递归层是否已经使用过某个数字,防止同层次中重复选择相同的元素
//递归函数不需要返回值,结果会直接存入 result 列表中
private void backtracking(int[] nums, int startIndex, List<Integer> path){}
1
2
3
if(path.size() >= 2){
result.add(new ArrayList<>(path));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Set<Integer> used = new HashSet<>(); //记录当前层已经使用过的元素来去重

for(int i = startIndex; i < nums.length; i++){ //单层搜索过程
if(used.contains(nums[i])){//去重
continue;//跳过
}
if(!path.isEmpty() && nums[i] < path.getLast()){//判断递增
continue;
}

path.add(nums[i]);
used.add(nums[i]);//记录元素防止本层重复使用
backtracking(nums, i + 1, path);//递归进入下一层
path.removeLast();//回溯
}

完整的回溯代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution { 

List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums, 0, new ArrayList<>());
return result;
}
private void backtracking(int[] nums, int startIndex, List<Integer> path){
if(path.size() >= 2){//终止条件
result.add(new ArrayList<>(path));
}

Set<Integer> used = new HashSet<>(); //记录当前层已经使用过的元素来去重

for(int i = startIndex; i < nums.length; i++){ //单层搜索过程
if(used.contains(nums[i])){//去重
continue;//跳过
}
if(!path.isEmpty() && nums[i] < path.getLast()){//判断递增
continue;
}

path.add(nums[i]);
used.add(nums[i]);//记录元素防止本层重复使用
backtracking(nums, i + 1, path);//递归进入下一层
path.removeLast();//回溯
}
}
}

全排列

leetCode题目链接

给定一个没有重复数字的序列,返回其所有可能的全排列🤔

1728959555328

我们来思路梳理

我们来进一步梳理回溯三要素

1
2
3
4
5
6
7
//nums: 输入的整数数组
//used: 一个布尔数组,记录哪些数字已经被使用
//path: 当前排列的路径
//result: 存储所有可能排列的列表
//递归函数不需要返回值,结果会直接存入result列表中
List<List<Integer>> result = new ArrayList<>();
private void backtracking(int[] nums, boolean[] used, List<Integer> path){}
1
2
3
4
if(path.size() == nums.length){
result.add(new ArrayList<>(path));
return;
}
1
2
3
4
5
6
7
8
9
10
for(int i = 0; i < nums.length; i++){
if(used[i])continue;//如果数字使用过则跳过
//选择当前数字加入排列
path.add(nums[i]);
used[i] = true;
backtracking(nums, used, path);//递归继续构造剩余数字的排列
//回溯
used[i] = false;
path.removeLast();
}

完整的回溯代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used = new boolean[nums.length];
backtracking(nums, used, new ArrayList<>());
return result;
}
private void backtracking(int[] nums, boolean[] used, List<Integer> path){
if(path.size() == nums.length){
result.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i++){
if(used[i])continue;//如果数字使用过则跳过
//选择当前数字加入排列
path.add(nums[i]);
used[i] = true;
backtracking(nums, used, path);//递归继续构造剩余数字的排列
//回溯
used[i] = false;
path.removeLast();
}
}
}

全排列II

LeetCode题目链接

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列

1728959673905

我们来进行思路梳理

我们来进一步梳理回溯三要素

1
2
3
4
5
6
7
//nums: 输入的整数数组(包含重复数字)
//used: 一个布尔数组,记录哪些数字已经被使用
//path: 当前排列的路径
//result: 存储所有不重复排列的列表
//递归函数不需要返回值,结果会直接存入 result 列表中
List<List<Integer>> result = new ArrayList<>();
private void backtracking(int[] nums, boolean[] used, List<Integer> path) {}
1
2
3
4
if (path.size() == nums.length) {
result.add(new ArrayList<>(path)); // 将当前排列加入结果集
return;
}
1
2
3
4
5
6
7
8
9
10
11
for(int i = 0; i < nums.length; i++){
//同一层中如果当前数字和前一个数字相同,且前一个未使用过,则跳过
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;//去重
if (used[i])continue; //如果当前数字已经被使用过,跳过该数字
path.add(nums[i]);
used[i] = true;
backtracking(nums, used, path);
//回溯
used[i] = false;
path.remove(path.size() - 1);
}

完整的回溯代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];
backtracking(nums, used, new ArrayList<>());
return result;
}

private void backtracking(int[] nums, boolean[] used, List<Integer> path) {
//终止条件
if (path.size() == nums.length) {
result.add(new ArrayList<>(path)); // 将当前排列加入结果集
return;
}

//单层搜索过程
for(int i = 0; i < nums.length; i++){
//同一层中如果当前数字和前一个数字相同,且前一个未使用过,则跳过
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;//去重
if (used[i])continue; //如果当前数字已经被使用过,跳过该数字
path.add(nums[i]);
used[i] = true;
backtracking(nums, used, path);
//回溯
used[i] = false;
path.remove(path.size() - 1);
}
}
}

总结

今天的回溯就学到这里啦,去重的话又有了不一样的处理方式,但处理逻辑大差不差,大家只需梳理清楚每层递归与回溯即可,奥利给✊✊✊