文章目录
- ❇️Day 29 第七章 回溯算法 part05
- ✴️今日内容
- ❇️491.递增子序列
- 自己的思路
- 随想录思路
- 自己的代码
- ❇️46.全排列
- 思路
- 代码
- 流程
- ❇️47.全排列 II
- 思路
- 代码
❇️Day 29 第七章 回溯算法 part05
✴️今日内容
- 491.递增子序列
- 46.全排列
- 47.全排列 II
❇️491.递增子序列
- 本题和大家刚做过的 90.子集II 非常像,但又很不一样,很容易掉坑里。
- 题目链接:https://leetcode.cn/problems/non-decreasing-subsequences/
- 视频讲解:https://www.bilibili.com/video/BV1EG4y1h78v
- 文章链接:https://programmercarl.com/0491.%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.html
自己的思路
- 先给数组排序
- 使用visited数组判断相同数是否被访问过
- 当nums长度大等于2时添加到res中
- 去重:因为不能排序,所以去重更复杂了,用visited不能通过了
随想录思路
所以要从集合中判断当前数在前面的集合中有没有出现过,所以自然想到哈希表,
- 创建一个set
- 每取一个元素就把元素放到set中
自己的代码
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
dfs(nums, 0);
return res;
}
public void dfs(int[] nums, int start){
if(path.size() >= 2){
res.add(new ArrayList<>(path));
}
//子集问题可以省掉终止条件因为当start >= nums.length时不会进入for循环
//if(start >= nums.length) return;
//在每一个for循环前定义一个set来表示已经被选过数的集合
HashSet<Integer> usedSet = new HashSet<>();
for (int i = start; i < nums.length; i++) {
if(!path.isEmpty() && path.get(path.size() - 1) > nums[i] || usedSet.contains(nums[i])) {
continue;
}
usedSet.add(nums[i]);
path.add(nums[i]);
dfs(nums, i + 1);
path.removeLast();
}
}
}
❇️46.全排列
- 本题重点感受一下,排列问题 与 组合问题,组合总和,子集问题的区别。 为什么排列问题不用 startIndex
- 题目链接:https://leetcode.cn/problems/permutations/
- 视频讲解:https://www.bilibili.com/video/BV19v4y1S79W
- 文章链接:https://programmercarl.com/0046.%E5%85%A8%E6%8E%92%E5%88%97.html
思路
先固定数组中的一个值,然后再固定排列剩下的值
所以需要一个参数来表示数组中的数有没有被固定
代码
public static List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
dfs(nums, new boolean[nums.length], new LinkedList<>(), res);
return res;
}
public static void dfs(int[] nums, boolean[] visited, LinkedList<Integer> stack, List<List<Integer>> res){
//结束条件
if(stack.size() == nums.length){
res.add(new ArrayList<>(stack));
return;
}
//遍历nums数组,发现没有被使用的数字,则将其标记为使用,并加入stack
for (int i = 0; i < nums.length; i++) {
if (!visited[i]) {
stack.push(nums[i]);
visited[i] = true;
dfs(nums, visited, stack, res);
//回溯
visited[i] = false;
stack.pop();
}
}
}
流程
❇️47.全排列 II
- 本题 就是我们讲过的 40.组合总和II 去重逻辑 和 46.全排列 的结合,可以先自己做一下,然后重点看一下 文章中 我讲的拓展内容。 used[i - 1] == true 也行,used[i - 1] == false 也行
- 题目链接:https://leetcode.cn/problems/permutations-ii/
- 视频讲解:https://www.bilibili.com/video/BV1R84y1i7Tm
- 文章链接:https://programmercarl.com/0047.%E5%85%A8%E6%8E%92%E5%88%97II.html
思路
规定:遇到相同的几个数,先固定第一个,再固定第二个
这样做就需要给数组排个序使相同的数字挨在一起
这种操作也叫剪枝
代码
public static List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
dfs(nums, new boolean[nums.length], new LinkedList<>(), res);
return res;
}
public static void dfs(int[] nums, boolean[] visited, LinkedList<Integer> stack, List<List<Integer>> res){
//结束条件
if(stack.size() == nums.length){
res.add(new ArrayList<>(stack));
return;
}
//遍历nums数组,发现没有被使用的数字,则将其标记为使用,并加入stack
for (int i = 0; i < nums.length; i++) {
//▶️找出重复的数字,且上一个与当前数字相等的数字没有被固定则跳过当前循环
if(i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]){
continue;
}
if (!visited[i]) {
stack.push(nums[i]);
visited[i] = true;
dfs(nums, visited, stack, res);
//回溯
visited[i] = false;
stack.pop();
}
}
}