leetcode47.全排列II:HashSet层去重与used数组枝去重的双重保障

news2025/6/7 22:49:05

一、题目深度解析与重复排列问题

题目描述

给定一个可能包含重复数字的数组nums,返回其所有不重复的全排列。解集不能包含重复的排列,且排列可以按任意顺序返回。例如:

  • 输入:nums = [1,1,2]
  • 输出:[[1,1,2],[1,2,1],[2,1,1]]

核心挑战:

  1. 重复排列消除:相同元素的不同排列路径可能生成相同结果
  2. 元素重复处理:数组中存在重复元素,需避免重复选择
  3. 排列唯一性:确保每个排列唯一且包含所有元素

二、回溯解法的核心实现与去重逻辑

完整回溯代码实现

class Solution {
    List<Integer> temp = new LinkedList<>();  // 存储当前排列
    List<List<Integer>> res = new ArrayList<>();  // 存储所有唯一排列
    boolean[] used;  // 标记元素是否已使用

    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);  // 排序是去重的前提
        used = new boolean[nums.length];
        backtracking(nums);
        return res;
    }

    public void backtracking(int[] nums) {
        if (temp.size() == nums.length) {  // 终止条件:生成完整排列
            res.add(new ArrayList<>(temp));
            return;
        }

        HashSet<Integer> hs = new HashSet<>();  // 同层去重集合
        
        for (int i = 0; i < nums.length; i++) {
            // 条件1:元素已使用则跳过(枝去重)
            // 条件2:同层中重复元素只选第一个(层去重)
            if (used[i] || hs.contains(nums[i])) {
                continue;
            }
            
            hs.add(nums[i]);  // 记录当前层已选元素
            used[i] = true;   // 标记元素为已使用
            temp.add(nums[i]); // 选择当前元素
            backtracking(nums); // 递归生成后续排列
            temp.removeLast();  // 回溯:撤销选择
            used[i] = false;  // 回溯:重置使用标记
        }
    }
}

核心去重组件解析:

  1. 排序预处理

    • Arrays.sort(nums)确保重复元素相邻,为去重提供条件
  2. used数组

    • 标记元素是否已在当前排列中使用,避免同一排列中重复使用元素(枝去重)
  3. HashSet

    • 在每层循环中新建,记录当前层已选元素
    • 避免同一层中重复选择相同元素(层去重)

三、HashSet层去重与used数组枝去重的协同工作

1. 层去重:HashSet的核心作用

重复排列产生场景:
  • 当数组中有重复元素时,同一层中选择相同元素会生成相同排列
  • 例如:数组[1,1,2]中,第一层选择第一个1和第二个1会生成相同排列[1,1,2]
HashSet去重逻辑:
HashSet<Integer> hs = new HashSet<>();  // 每层新建HashSet
if (hs.contains(nums[i])) continue;  // 同层重复元素跳过
hs.add(nums[i]);  // 记录当前层已选元素
  • 作用域:每个循环层的HashSet独立,仅影响当前层的元素选择
  • 示例:在第一层循环中,选择第一个1后,HashSet记录1,第二个1会被跳过,避免同层重复

2. 枝去重:used数组的核心作用

元素使用控制:
if (used[i]) continue;  // 已使用元素跳过
used[i] = true;  // 标记为已使用
// ...递归处理
used[i] = false;  // 回溯时重置标记
  • 跨层控制:确保每个元素在一个排列中仅使用一次
  • 示例:父层选择1后,子层无法再选择1,保证排列合法性

3. 双重去重的协同效应

条件组合:
if (used[i] || hs.contains(nums[i])) continue;
  • 枝去重:used数组防止同一排列中重复使用元素
  • 层去重:HashSet防止同一层中重复选择元素
  • 共同作用:确保排列唯一且合法

四、去重流程深度模拟:以输入[1,1,2]为例

递归调用树与去重节点:

backtracking([1,1,2])
├─ temp=[]
│  ├─ i=0(元素1):未使用,HashSet空,选择1 → used[0]=true, temp=[1]
│  │  ├─ 第二层循环,HashSet新建
│  │  │  ├─ i=0(元素1):used[0]=true,跳过
│  │  │  ├─ i=1(元素1):HashSet空,选择1 → used[1]=true, temp=[1,1]
│  │  │  │  ├─ 第三层循环,HashSet新建
│  │  │  │  │  ├─ i=0-2:仅i=2(元素2)未使用,选择2 → temp=[1,1,2],收集
│  │  │  │  └─ 回溯,used[2]=false
│  │  │  └─ 回溯,used[1]=false, temp=[1]
│  │  ├─ i=2(元素2):HashSet空,选择2 → used[2]=true, temp=[1,2]
│  │  │  └─ 第三层循环选择1(i=0或1),生成[1,2,1],收集
│  │  └─ 回溯,used[0]=false, temp=[]
│  ├─ i=1(元素1):HashSet已含1(i=0选过),跳过
│  └─ i=2(元素2):HashSet空,选择2 → used[2]=true, temp=[2]
│     └─ 第二层循环选择1(i=0或1),生成[2,1,1],收集

关键去重步骤:

  1. 第一层循环

    • i=0选1,HashSet={1}
    • i=1选1时,HashSet.contains(1)为true,跳过
    • i=2选2,正常处理
  2. 第二层循环(temp=[1])

    • i=0的1已使用,跳过
    • i=1的1未使用,但HashSet空,选择1(因为是新层的HashSet)
    • i=2的2未使用,选择2
  3. 第三层循环(temp=[1,1])

    • 只能选2,生成[1,1,2]

五、去重条件的数学证明

命题:双重去重确保排列唯一

证明步骤:
  1. 排序保证相邻重复:排序后重复元素相邻,便于同层检测
  2. used数组保证枝唯一:每个元素在排列中仅使用一次
  3. HashSet保证层唯一
    • 同层中相同元素仅选第一个
    • 不同层中相同元素允许选择(属于不同路径)
  4. 排列唯一性
    • 同层重复被HashSet过滤
    • 跨层重复因路径不同,生成不同排列(但实际可能相同?不,因为排序后跨层重复元素会被正确区分)
反证法:

假设存在重复排列,必因:

  • 同层选择相同元素:被HashSet过滤
  • 跨层选择相同元素:但排序后跨层选择相同元素时,路径不同但排列可能相同,此时如何处理?
    实际上,由于排序后重复元素相邻,跨层选择相同元素时,used数组确保每个元素仅用一次,而HashSet确保同层不重复,最终所有排列唯一。

六、算法复杂度分析

1. 时间复杂度

  • O(n × n!)
    • 排列总数为n!(去重后最多n!个)
    • 每个排列需O(n)时间复制到结果集
    • 排序时间O(n log n),总体为O(n × n! + n log n)

2. 空间复杂度

  • O(n)
    • 递归栈深度最大为n
    • used数组长度为n
    • temp列表长度最多为n
    • 结果集空间O(n × n!)

七、核心技术点总结:双重去重的关键要素

1. 排序预处理

  • 作用:使重复元素相邻,为同层去重提供条件
  • 必要性:未排序时重复元素不相邻,无法正确检测同层重复

2. HashSet的层去重

  • 时机:每层循环新建HashSet,仅作用于当前层
  • 逻辑:同层中相同元素只选第一个,避免生成重复排列

3. used数组的枝去重

  • 作用域:跨层跟踪元素使用状态
  • 逻辑:确保每个元素在排列中仅使用一次,保证排列合法性

八、常见误区与优化建议

1. 忽略排序的重要性

  • 错误做法:未排序直接去重
    // 缺少Arrays.sort(nums)
    if (used[i] || hs.contains(nums[i])) continue; // 错误,无法正确去重
    
  • 后果:重复元素不相邻,HashSet无法检测同层重复

2. HashSet位置错误

  • 误区:将HashSet放在递归函数外
    HashSet<Integer> hs = new HashSet<>(); // 错误,跨层共享导致去重过度
    
  • 正确做法:放在循环内,每层独立

3. 优化建议:索引判断去重(替代HashSet)

public void backtracking(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        // 同层去重:i>0且nums[i]==nums[i-1]且used[i-1]==false
        if (used[i] || (i > 0 && nums[i] == nums[i-1] && !used[i-1])) {
            continue;
        }
        // ...
    }
}
  • 优势:无需HashSet,直接通过索引和used数组判断
  • 原理:排序后,同层中前一个相同元素未使用时,当前元素跳过

九、总结:层去重与枝去重的协同设计

本算法通过排序、HashSet层去重和used数组枝去重的三重保障,高效解决了含重复元素的全排列问题,核心在于:

  1. 排序预处理:为同层去重提供基础,使重复元素相邻
  2. HashSet层控制:确保同一层中相同元素仅选第一个,避免重复排列
  3. used数组枝控制:跨层跟踪元素使用状态,保证排列合法性

理解这种解法的关键是区分层去重与枝去重的不同作用范围:层去重避免同一层中的重复选择,枝去重避免同一排列中的重复使用。两者结合排序策略,形成了完整的去重体系,是处理含重复元素排列问题的经典方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2403430.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5.Nginx+Tomcat负载均衡群集

Tomcat服务器应用场景&#xff1a;tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP程序的首选。一般来说&#xff0c;Tomcat虽然和Apache或…

React项目的状态管理:Redux Toolkit

目录 1、搭建环境 2、Redux Toolkit 包含了什么 3、使用示例 &#xff08;1&#xff09;创建user切片 &#xff08;2&#xff09;合并切片得到store &#xff08;3&#xff09;配置store和使用store 使用js来编写代码&#xff0c;方便理解一些 1、搭建环境 首先&#xf…

跨界破局者鲁力:用思辨与创新重塑汽车流通行业标杆

来源&#xff1a;投资家 在汽车流通行业深度变革的浪潮中&#xff0c;东莞东风南方汽车销售服务有限公司塘厦分公司总经理鲁力历经近二十年行业深耕&#xff0c;构建了一条从汽车销售顾问到区域运营掌舵者的进阶范本。作为东风日产体系内兼具理论建构与实战穿透力的标杆管理者…

OS11.【Linux】vim文本编辑器

目录 1.四种模式 命令模式 几个命令 插入模式 底行模式 一图展示三种模式之间的关系 2.分屏(多文件操作) 3.配置vim的原理 4.脚本一键配置vim CentOS 7 x86_64 其他发行版 5.NeoVim(推荐) vim文本编辑器是一个多模式的编辑器,因此先介绍它的四种模式 附vim的官网:…

基于SFC的windows系统损坏修复程序

前言 在平时使用Windows操作系统时会遇到很多因为系统文件损坏而出现的错误 例如:系统应用无法打开 系统窗口(例如开始菜单)无法使用 电脑蓝屏或者卡死 是如果想要修复很多人只能想到重装系统。但其实Windows有一个内置的系统文件检查器可以修复此类错误。 原理 SFC命令…

WAF绕过,网络层面后门分析,Windows/linux/数据库提权实验

一、WAF绕过文件上传漏洞 win7&#xff1a;10.0.0.168 思路&#xff1a;要想要绕过WAF&#xff0c;第一步是要根据上传的内容找出来被拦截的原因。对于文件上传有三个可以考虑的点&#xff1a;文件后缀名&#xff0c;文件内容&#xff0c;文件类型。 第二步是根据找出来的拦截原…

Vue 3 弹出式计算器组件(源码 + 教程)

&#x1f9ee; Vue 3 弹出式计算器组件&#xff08;源码 教程&#xff09; &#x1f4cc; 建议收藏 点赞 关注&#xff0c;本组件支持加减乘除、双向绑定、计算过程展示&#xff0c;适用于表单辅助输入场景。 &#x1f527; 一、完整源码&#xff08;复制即用&#xff09; …

监测预警系统重塑隧道安全新范式

在崇山峻岭的脉络间延伸的隧道&#xff0c;曾是交通安全的薄弱环节。智慧隧道监测预警系统的诞生&#xff0c;正在彻底改变这种被动防御格局&#xff0c;通过数字神经网络的构建&#xff0c;为地下交通动脉注入智能守护基因。 一、安全防控体系的质变升级 1.风险感知维度革命…

技巧小结:外部总线访问FPGA寄存器

概述 需求&#xff1a;stm32的fsmc总线挂载fpga&#xff0c;stm32需要访问fpga内部寄存器 1、分散加载文件将变量存放到指定地址即FPGA寄存器地址 sct文件指定变量存储地址&#xff0c;从而可以直接访问外设&#xff0c;&#xff08;28335也可以&#xff0c;不过用的是cmd文件…

jenkins集成gitlab发布到远程服务器

jenkins集成gitlab发布到远程服务器 前面我们讲了通过创建maven项目部署在jenkins本地服务器&#xff0c;这次实验我们将部署在远程服务器&#xff0c;再以nginx作为前端项目做一个小小的举例 1、部署nginx服务 [rootweb ~]# docker pull nginx [rootweb ~]# docker images …

当主观认知遇上机器逻辑:减少大模型工程化中的“主观性”模糊

一、人类与机器的认知差异 当自动驾驶汽车遇到紧急情况需要做出选择时&#xff0c;人类的决策往往充满矛盾&#xff1a;有人会优先保护儿童和老人&#xff0c;有人坚持"不主动变道"的操作原则。这种差异背后&#xff0c;体现着人类特有的情感判断与价值选择。而机器的…

会计 - 金融负债和权益工具

一、金融负债和权益工具区分的基本原则 (1)是否存在无条件地避免交付现金或其他金融资产的合同义务 如果企业不能无条件地避免以交付现金或其他金融资产来履行一项合同义务,则该合同义务符合金融负债的义务。 常见的该类合同义务情形包括:- 不能无条件避免的赎回; -强制…

Dify工具插件开发和智能体开发全流程

想象一下&#xff0c;你正在开发一个 AI 聊天机器人&#xff0c;想让它能实时搜索 Google、生成图像&#xff0c;甚至自动规划任务&#xff0c;但手动集成这些功能耗时又复杂。Dify 来了&#xff01;这个开源的 AI 应用平台让你轻松开发工具插件和智能体策略插件&#xff0c;快…

AI书签管理工具开发全记录(十三):TUI基本框架搭建

文章目录 AI书签管理工具开发全记录&#xff08;十三&#xff09;&#xff1a;TUI基本框架搭建前言 &#x1f4dd;1.TUI介绍 &#x1f50d;2. 框架选择 ⚙️3. 功能梳理 &#x1f3af;4. 基础框架搭建⚙️4.1 安装4.2 参数设计4.3 绘制ui4.3.1 设计结构体4.3.2 创建头部4.3.3 创…

初识结构体,整型提升及操作符的属性

目录 一、结构体成员访问操作符1.1 结构体二、操作符的属性&#xff1a;优先级、结合性2.1 优先级2.2 结合性C 运算符优先级 三、表达式求值3.1 整型提升3.2 算数转化 总结 一、结构体成员访问操作符 1.1 结构体 C语言已经提供了内置类型&#xff0c;如&#xff1a;char,shor…

检测到 #include 错误。请更新 includePath。已为此翻译单元(D:\软件\vscode\test.c)禁用波形曲线

原文链接&#xff1a;【VScodeMinGw】安装配置教程 下载mingw64 打开可以看到bin文件夹下是多个.exe文件&#xff0c;gcc.exe地址在环境配置中要用到 原文链接&#xff1a;VSCode中出现“#include错误&#xff0c;请更新includePath“问题&#xff0c;解决方法 重新VScode后…

2025年,百度智能云打响AI落地升维战

如果说从AI到Agent是对于产品落地形态的共识&#xff0c;那么如今百度智能云打响的恰是一个基于Agent进行TO B行业表达的AI生产力升维战。 在这个新的工程体系能力里&#xff0c;除了之前百度Create大会上提出的面向Agent的RAG能力等通用能力模块&#xff0c;对更为专业、个性…

Seed1.5-VL登顶,国产闭源模型弯道超车丨多模态模型5月最新榜单揭晓

随着图像、文本、语音、视频等多模态信息融合能力的持续增强&#xff0c;多模态大模型在感知理解、逻辑推理和内容生成等任务中的综合表现不断提升&#xff0c;正在展现出愈发接近人类的智能水平。多模态能力也正在从底层的感知理解&#xff0c;迈向具备认知、推理、决策能力的…

第3章——SSM整合

一、整合持久层框架MyBatis 1.准备数据库表及数据 创建数据库&#xff1a;springboot 使用IDEA工具自带的mysql插件来完成表的创建和数据的准备&#xff1a; 创建表 表创建成功后&#xff0c;为表准备数据&#xff0c;如下&#xff1a; 2.创建SpringBoot项目 使用脚手架创建…

VTK 显示文字、图片及2D/3D图

1. 基本环境设置 首先确保你已经安装了VTK库&#xff0c;并配置好了C开发环境。 #include <vtkSmartPointer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkRenderer.h> 2. 显示文字 2D文字 #include &l…