算法训练营(day29)

回溯算法模板

1
2
3
4
5
6
7
8
9
10
11
12
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}

491. 递增子序列

题目链接:https://leetcode.cn/problems/non-decreasing-subsequences/description/

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

解题思路

解题过程:回溯

  1. 定义终止条件:当子序列 path内元素数量大于1时, 放入 result
  2. 定义 map 记录 元素是否已使用
  3. 定义循环条件
    • 横向:从 startIndex 遍历到 nums.length
    • 纵向:如果 nums[i] < path.getLast() 或者 map.getOrDefault(nums[i], 0) >= 1 , 跳过 nums[i]
  4. 回溯:回滚 path 的末位

详细代码

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
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();

public List<List<Integer>> findSubsequences(int[] nums) {
backTrack(nums, 0);
return result;
}

public void backTrack(int[] nums, int startIndex){
if(path.size() > 1){
result.add(new ArrayList<>(path));
}
Map<Integer, Integer> map = new HashMap<>();
for(int i = startIndex; i < nums.length; i++){
if(!path.isEmpty() && nums[i] < path.getLast()){
continue;
}
if(map.getOrDefault(nums[i], 0) >= 1){
continue;
}
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
path.add(nums[i]);
backTrack(nums, i + 1);
path.removeLast();
}
}
}

46. 全排列

题目链接:https://leetcode.cn/problems/permutations/description/

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

解题思路

解题过程:排列有序

  1. 定义终止条件:当 path 长度等于 nums.length 时,放入 result
  2. 定义循环条件
    • 横向:从 startIndex 遍历到 nums.length
    • 纵向:排列组合 nums 元素
  3. 回溯:回滚 path 的末位元素

注意:解法二的 used 表示的是 在 当前下标 处该元素的使用情况,避免重复选用

详细代码

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();

public List<List<Integer>> permute(int[] nums) {
backTrack(nums, path);
return result;
}

public void backTrack(int[] nums, LinkedList<Integer> path){
if(path.size() == nums.length){
result.add(new ArrayList<>(path));
}
for(int i = 0; i < nums.length; i++){
if(path.contains(nums[i])){
continue;
}
path.add(nums[i]);
backTrack(nums, path);
path.removeLast();
}
}
}

解法二:使用 boolean[] used 去重

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
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
backTrack(nums);
return result;
}

public void backTrack(int[] nums){
if(path.size() == nums.length){
result.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i++){
if(used[i]){
continue;
}
used[i] = true;
path.add(nums[i]);
backTrack(nums);
path.removeLast();
used[i] = false;
}
}
}

47. 全排列 II

题目链接:https://leetcode.cn/problems/permutations-ii/description/

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

解题思路

解题过程:排列有序

  1. nums 序列进行排序,定义 used 判断使用情况
  2. 定义终止条件:当 path 长度等于 nums.length 时,放入 result
  3. 定义循环条件
    • 横向:从 startIndex 遍历到 nums.length
    • 纵向:排列组合 nums 元素
  4. 回溯:回滚 path 的末位元素

注意used 表示的是 在 当前下标 处该元素的使用情况,避免重复选用

详细代码

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
31
32
33
34
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
used = new boolean[nums.length];
Arrays.fill(used, false);
backTrack(nums);
return result;
}

public void backTrack(int[] nums){
if(path.size() == nums.length){
result.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i++){
if(used[i]){
continue;
}
if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false){
continue;
}
if(used[i] == false){
used[i] = true;
path.add(nums[i]);
backTrack(nums);
path.removeLast();
used[i] = false;
}
}
}
}