当前位置: 首页 > news >正文

LeetCode47

LeetCode47

目录

  • 题目描述
  • 示例
  • 思路分析
  • 代码段
  • 代码逐行讲解
  • 复杂度分析
  • 总结的知识点
  • 整合
  • 总结

题目描述

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


示例

示例 1

输入:

nums = [1, 1, 2]

输出:

[
  [1, 1, 2],
  [1, 2, 1],
  [2, 1, 1]
]

示例 2

输入:

nums = [1, 2, 3]

输出:

[
  [1, 2, 3],
  [1, 3, 2],
  [2, 1, 3],
  [2, 3, 1],
  [3, 1, 2],
  [3, 2, 1]
]

思路分析

问题核心

我们需要找到数组中所有不重复的全排列。由于数组可能包含重复数字,因此需要避免生成重复的排列。

思路拆解

  1. 排序
    • 将数组排序,使相同的数字相邻,方便后续去重。
  2. 深度优先搜索(DFS)
    • 使用 DFS 遍历所有可能的排列。
  3. 剪枝
    • 在 DFS 过程中,如果当前数字与前一个数字相同,并且前一个数字未被使用,则跳过当前数字,避免重复排列。
  4. 回溯
    • 在 DFS 过程中,使用 visited 数组标记已使用的数字,并在回溯时取消标记。

代码段

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums); 
        List<List<Integer>> res = new ArrayList<>();
        dfs(nums, new boolean[nums.length], new LinkedList<>(), res); 
        return res;
    }

    private 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;
        }

        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();
            }
        }
    }
}

在这里插入图片描述


代码逐行讲解

1. 排序

Arrays.sort(nums);
  • 将数组排序,使相同的数字相邻,方便后续去重。

2. 初始化结果列表

List<List<Integer>> res = new ArrayList<>();
  • res 用于存储所有不重复的排列。

3. 调用 DFS

dfs(nums, new boolean[nums.length], new LinkedList<>(), res);
  • 调用 DFS 函数,传入数组 numsvisited 数组(用于标记已使用的数字)、stack(用于存储当前排列)和结果列表 res

4. DFS 函数

private static void dfs(int[] nums, boolean[] visited, LinkedList<Integer> stack, List<List<Integer>> res) {
  • DFS 函数的定义,用于递归生成排列。

5. 找到一个排列

if (stack.size() == nums.length) {
    res.add(new ArrayList<>(stack));
    return;
}
  • 如果当前排列的长度等于数组长度,说明找到一个完整的排列,将其加入结果列表。

6. 遍历数组

for (int i = 0; i < nums.length; i++) {
  • 遍历数组中的每个数字,尝试将其加入当前排列。

7. 剪枝:避免重复排列

if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
    continue;
}
  • 如果当前数字与前一个数字相同,并且前一个数字未被使用,则跳过当前数字,避免重复排列。

8. 加入当前数字

if (!visited[i]) {
    stack.push(nums[i]);
    visited[i] = true;
  • 如果当前数字未被使用,将其加入当前排列,并标记为已使用。

9. 递归

dfs(nums, visited, stack, res);
  • 递归调用 DFS,继续生成下一个数字的排列。

10. 回溯

visited[i] = false;
stack.pop();
  • 回溯时取消当前数字的标记,并将其从当前排列中移除。

复杂度分析

时间复杂度

  • 全排列的数量为 O(n!),其中 n 是数组的长度。
  • 每次生成一个排列需要 O(n) 的时间。
  • 因此,总时间复杂度为 O(n * n!)

空间复杂度

  • 需要存储所有排列,空间复杂度为 O(n * n!)
  • 递归栈的深度为 n,因此额外空间复杂度为 O(n)

总结的知识点

1. 排序

  • 使用 Arrays.sort 对数组进行排序,方便后续去重。

2. 深度优先搜索(DFS)

  • 使用 DFS 遍历所有可能的排列。

3. 剪枝

  • 在 DFS 过程中,通过条件判断避免生成重复的排列。

4. 回溯

  • 在 DFS 过程中,使用 visited 数组标记已使用的数字,并在回溯时取消标记。

整合

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums); // 排序
        List<List<Integer>> res = new ArrayList<>(); // 结果列表
        dfs(nums, new boolean[nums.length], new LinkedList<>(), res); // DFS
        return res;
    }

    private 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 数组
        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(); // 移除当前数字
            }
        }
    }
}

总结

这段代码通过排序、DFS 和剪枝,能够高效地生成所有不重复的全排列。

相关文章:

  • 【ISO 14229-1:2023 UDS诊断(会话控制0x10服务)测试用例CAPL代码全解析⑩】
  • AI服务器散热黑科技:让芯片“冷静”提速
  • linux网络编程(1.5w字+内部程序理解网络)
  • 7-Zip Final绿色版:高效压缩解压缩工具
  • 机器学些|实战?
  • 基于Java+SpringBoot+Vue的前后端分离的校园闲置物品交易网站
  • [Windows] Win7也能控制安卓手机屏幕(手机镜像投屏):scrcpy
  • PhotoShop批处理
  • Redis7——基础篇(三)
  • 3分钟了解内外网文件传输:常见方法、注意事项有哪些?
  • 【Scrapy】Scrapy教程5——第一个Scrapy项目
  • LeetCode 2080.区间内查询数字的频率:哈希表+二分查找
  • 在线OJ答题平台功能实现
  • 基于JavaWeb的公务员招考信息发布平台(源码+文档)
  • 调用openssl实现加解密算法
  • git删除本地分支
  • python继承中super() 不是简单的“调用父类”,而是调用 MRO 里的下一个类
  • LeetCode每日精进:225.用队列实现栈
  • 「pandas」Pandas 基本数据操作、 索引、赋值、排序
  • 网络工程师 (45)网际控制报文协议ICMP
  • 长和获准出售巴拿马运河港口以外的港口?外交部:该报道没有依据
  • 牧草之王苜蓿的江南驯化史
  • 申活观察|咖香涌动北外滩,带来哪些消费新想象?
  • 五部门:开展新就业形态劳动者劳动权益保障水平提升专项行动
  • 摩根大通任命杜峯为亚太区副主席,加码中国市场业务布局
  • “80后”杨占旭已任辽宁阜新市副市长,曾任辽宁石油化工大学副校长