摘要
本文主要介绍了LeetCode回溯算法的几个题目,包括491.递增子序列、46.全排列、47.全排列 II。
1、491.递增子序列
1.1 思路
- 首先检查路径
path的长度是否大于等于 2,如果是,就将当前路径添加到结果列表list中,因为题目要求至少包含两个元素的递增子序列。 - 之后,使用了一个
Set集合set来进行横向去重。 在同一层递归中,如果当前元素已经被添加到path中,就跳过它,以确保不会有重复的元素。 - 接下来,检查了递增条件。只有当
path中的元素为空或者当前元素大于或等于path中的最后一个元素时,才将当前元素添加到path中,以保持递增。 - 然后,递归地调用
doFindSubsequences函数,从下一个位置i+1开始搜索可能的递增子序列。 - 最后,需要回溯,即在完成当前递归后,将
path中的最后一个元素删除,以便继续搜索其他可能的子序列。
1、为什么90.子集II的横向去重方式在491.递增子序列不行?
在90.子集II中我们是通过排序,再加一个标记数组来达到去重的目的。
而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。
所以不能使用之前的去重逻辑!
1.2 代码
public List<List<Integer>> findSubsequences(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
doFindSubsequences(list, path, nums, 0);
return list;
}
public void doFindSubsequences(List<List<Integer>> list, LinkedList<Integer> path, int[] nums, int start) {
if(path.size() >= 2) {
list.add(new ArrayList<>(path));
}
Set<Integer> set = new HashSet<>();
for(int i=start; i<nums.length; i++) {
// 横向去重
if(set.contains(nums[i])) {
continue;
}
// 保持递增
if(path.size() > 0 && nums[i] < path.getLast()) {
continue;
}
path.add(nums[i]);
set.add(nums[i]);
doFindSubsequences(list, path, nums, i+1);
path.removeLast();
}
}
2、46.全排列
2.1 思路
- 首先检查路径
path的长度是否等于输入数组nums的长度,如果是,说明已经生成了一个完整的排列,将其添加到结果列表list中。 - 接下来,它使用一个循环遍历
nums数组中的每个元素。如果当前元素已经在path中存在(即path.contains(nums[i])),则跳过,以确保生成的排列中没有重复的元素。 - 如果当前元素没有在
path中存在,它将当前元素添加到path中,并递归调用doPermute函数以生成下一个元素的排列。 - 在完成递归之后,它需要回溯,即将
path中的最后一个元素删除,以便继续生成其他可能的排列。
2.2 代码
解法1:通过判断path中是否存在数字,排除已经选择的数字
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
doPermute(list, path, nums);
return list;
}
public void doPermute(List<List<Integer>> list, LinkedList<Integer> path, int[] nums) {
if(path.size() == nums.length) {
list.add(new ArrayList<>(path));
return;
}
for(int i=0; i<nums.length; i++) {
// 如果path中已有,则跳过
if (path.contains(nums[i])) {
continue;
}
path.add(nums[i]);
doPermute(list, path, nums);
path.removeLast();
}
}
解法2:used数组
public List<List<Integer>> permute(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.fill(used, false);
List<List<Integer>> list = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
doPermute(list, path, used, nums);
return list;
}
public void doPermute(List<List<Integer>> list, LinkedList<Integer> path, boolean[] used, int[] nums) {
if(path.size() == nums.length) {
list.add(new ArrayList<>(path));
return;
}
for(int i=0; i<nums.length; i++) {
if (used[i]) {
continue;
}
path.add(nums[i]);
used[i] = true;
doPermute(list, path, used, nums);
path.removeLast();
used[i] = false;
}
}
3、47.全排列 II
3.1 思路
1、为什么i > 0 && nums[i] == nums[i-1] && used[i-1] == false就是树层上的去重?
因为回溯过来取重复的值时,那么前一个值自然是 false,所以
i > 0 && nums[i] == nums[i-1] && used[i-1] == false就是树层上的去重
3.2 代码
public List<List<Integer>> permuteUnique(int[] nums) {
// 定义used数组
boolean[] used = new boolean[nums.length];
Arrays.fill(used, false);
// 为了将重复的数字都放到一起,所以先进行排序
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
doPermuteUnique(list, path, used, nums);
return list;
}
public void doPermuteUnique(List<List<Integer>> list, LinkedList<Integer> path, boolean[] used, int[] nums) {
if(path.size() == nums.length) {
list.add(new ArrayList<>(path));
return;
}
for(int i=0; i<nums.length; i++) {
// used[i-1] == true,说明同⼀树⽀nums[i-1]使⽤过
// used[i-1] == false,说明同⼀树层nums[i-1]使⽤过
// 如果同⼀树层nums[i-1]使⽤过则直接跳过
if (i > 0 && nums[i] == nums[i-1] && used[i-1] == false) {
continue;
}
if(used[i]) {
continue;
}
path.add(nums[i]);
used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树枝重复使用
doPermuteUnique(list, path, used, nums);
path.removeLast();
used[i] = false;
}
}