【代码随想录算法训练营Day29】 491.递增子序列;46.全排列;47.全排列 II

本文介绍了如何使用回溯算法解决LeetCode上的递增子序列、全排列和全排列II问题,强调了哈希表在处理重复数字和去重中的应用,以及排列与组合的区别和剪枝策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

❇️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

自己的思路

  1. 先给数组排序
  2. 使用visited数组判断相同数是否被访问过
  3. 当nums长度大等于2时添加到res中
  4. 去重:因为不能排序,所以去重更复杂了,用visited不能通过了

随想录思路

在这里插入图片描述

所以要从集合中判断当前数在前面的集合中有没有出现过,所以自然想到哈希表,

  1. 创建一个set
  2. 每取一个元素就把元素放到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();
                }
        }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值