代码训练day25回溯p4
1.递增子序列
(1)不能排序 (2)回溯搜索不能重复,用hashset去重(3)注意搜索条件,递增,不重复
class Solution {
// 不能排序
List<List<Integer>> res = new ArrayList<>();
Deque<Integer> path = new ArrayDeque<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backtrack(nums, 0);
return res;
}
private void backtrack(int[] nums, int startIndex){
if (path.size() > 1) {// 终止条件,递增子序列大小至少为2
res.add(new ArrayList<>(path));
}
// 单层搜索逻辑
HashSet<Integer> uset = new HashSet<>();
for (int i = startIndex; i < nums.length; i++) {
if (!path.isEmpty() && path.getLast() > nums[i] || uset.contains(nums[i]))
continue;// 判断 path 不为空且队列递减 或 有重复跳过
uset.add(nums[i]);// 用于去重
path.add(nums[i]);
backtrack(nums, i + 1);
path.removeLast();// 回溯
}
}
}
2.全排列
元素不重复
深入理解回溯:
回溯的本质:通过状态恢复,遍历所有可能的决策分支。
回溯的撤销是对当前路径上元素标记状态的撤销,以免影响其他路径。
每一次for循环都代表不同的路径分支,所以for循环的末尾是对状态的回溯。
class Solution {
// 全排列是有序的,元素顺序不同排列也不同,不用startindex,用used标记已选择
List<List<Integer>> res = new ArrayList<>();
Deque<Integer> path = new ArrayDeque<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) return res;
used = new boolean[nums.length];
backtrack(nums);
return res;
}
private void backtrack(int[] nums) {
if (path.size() == nums.length) {// 终止条件 path 元素等于数组长度
res.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;
}
}
}
3.全排列II
数组元素可重复
class Solution {
// 全排列去重,元素顺序不同排列不同
// 去重的关键在于排序
List<List<Integer>> res = new ArrayList<>();
Deque<Integer> path = new ArrayDeque<>();
public List<List<Integer>> permuteUnique(int[] nums) {
if (nums.length == 0) return res;
boolean[] used = new boolean[nums.length];
Arrays.fill(used, false);
Arrays.sort(nums);
backtrack(nums, used);
return res;
}
private void backtrack(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
res.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;
//如果同⼀树⽀nums[i]没使⽤过开始处理
if (used[i] == false) {
used[i] = true; // 标记同一树枝nums[i]使用过,防止同路径重复使用
path.add(nums[i]);
backtrack(nums, used);
path.removeLast();
used[i] = false;
}
}
}
}