leetcode47.全排列II:HashSet层去重与used数组枝去重的双重保障
一、题目深度解析与重复排列问题
题目描述
给定一个可能包含重复数字的数组nums
,返回其所有不重复的全排列。解集不能包含重复的排列,且排列可以按任意顺序返回。例如:
- 输入:
nums = [1,1,2]
- 输出:
[[1,1,2],[1,2,1],[2,1,1]]
核心挑战:
- 重复排列消除:相同元素的不同排列路径可能生成相同结果
- 元素重复处理:数组中存在重复元素,需避免重复选择
- 排列唯一性:确保每个排列唯一且包含所有元素
二、回溯解法的核心实现与去重逻辑
完整回溯代码实现
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; // 回溯:重置使用标记}}
}
核心去重组件解析:
-
排序预处理:
Arrays.sort(nums)
确保重复元素相邻,为去重提供条件
-
used数组:
- 标记元素是否已在当前排列中使用,避免同一排列中重复使用元素(枝去重)
-
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],收集
关键去重步骤:
-
第一层循环:
- i=0选1,HashSet={1}
- i=1选1时,HashSet.contains(1)为true,跳过
- i=2选2,正常处理
-
第二层循环(temp=[1]):
- i=0的1已使用,跳过
- i=1的1未使用,但HashSet空,选择1(因为是新层的HashSet)
- i=2的2未使用,选择2
-
第三层循环(temp=[1,1]):
- 只能选2,生成[1,1,2]
五、去重条件的数学证明
命题:双重去重确保排列唯一
证明步骤:
- 排序保证相邻重复:排序后重复元素相邻,便于同层检测
- used数组保证枝唯一:每个元素在排列中仅使用一次
- HashSet保证层唯一:
- 同层中相同元素仅选第一个
- 不同层中相同元素允许选择(属于不同路径)
- 排列唯一性:
- 同层重复被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]==falseif (used[i] || (i > 0 && nums[i] == nums[i-1] && !used[i-1])) {continue;}// ...}
}
- 优势:无需HashSet,直接通过索引和used数组判断
- 原理:排序后,同层中前一个相同元素未使用时,当前元素跳过
九、总结:层去重与枝去重的协同设计
本算法通过排序、HashSet层去重和used数组枝去重的三重保障,高效解决了含重复元素的全排列问题,核心在于:
- 排序预处理:为同层去重提供基础,使重复元素相邻
- HashSet层控制:确保同一层中相同元素仅选第一个,避免重复排列
- used数组枝控制:跨层跟踪元素使用状态,保证排列合法性
理解这种解法的关键是区分层去重与枝去重的不同作用范围:层去重避免同一层中的重复选择,枝去重避免同一排列中的重复使用。两者结合排序策略,形成了完整的去重体系,是处理含重复元素排列问题的经典方案。
相关文章:
leetcode47.全排列II:HashSet层去重与used数组枝去重的双重保障
一、题目深度解析与重复排列问题 题目描述 给定一个可能包含重复数字的数组nums,返回其所有不重复的全排列。解集不能包含重复的排列,且排列可以按任意顺序返回。例如: 输入:nums [1,1,2]输出:[[1,1,2],[1,2,1],[2…...

5.Nginx+Tomcat负载均衡群集
Tomcat服务器应用场景:tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。一般来说,Tomcat虽然和Apache或…...

React项目的状态管理:Redux Toolkit
目录 1、搭建环境 2、Redux Toolkit 包含了什么 3、使用示例 (1)创建user切片 (2)合并切片得到store (3)配置store和使用store 使用js来编写代码,方便理解一些 1、搭建环境 首先…...

跨界破局者鲁力:用思辨与创新重塑汽车流通行业标杆
来源:投资家 在汽车流通行业深度变革的浪潮中,东莞东风南方汽车销售服务有限公司塘厦分公司总经理鲁力历经近二十年行业深耕,构建了一条从汽车销售顾问到区域运营掌舵者的进阶范本。作为东风日产体系内兼具理论建构与实战穿透力的标杆管理者…...
Druid连接池实现自定义数据库密码加解密功能详解
Druid连接池实现自定义数据库密码加解密功能详解 在企业级应用开发中,数据库密码的明文存储是一个显著的安全隐患。Druid作为阿里巴巴开源的高性能数据库连接池组件,提供了灵活的密码加密与解密功能,允许开发者通过自定义逻辑实现数据库密码…...

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

基于SFC的windows系统损坏修复程序
前言 在平时使用Windows操作系统时会遇到很多因为系统文件损坏而出现的错误 例如:系统应用无法打开 系统窗口(例如开始菜单)无法使用 电脑蓝屏或者卡死 是如果想要修复很多人只能想到重装系统。但其实Windows有一个内置的系统文件检查器可以修复此类错误。 原理 SFC命令…...
强化学习基础概念图文版笔记
📘 强化学习基础概念图文版笔记 1️⃣ 基本框架:Agent 与 Environment 🧠 核心角色: Agent(智能体):做出决策的“大脑”,根据当前状态选择动作。Environment(环境&…...
k8s下离线搭建elasticsearch
前提 已经完成k8s安装 已经完成相关组件如helm的安装 下载es的chart包 如下地址 https://helm.elastic.co/helm/elasticsearch/elasticsearch-版本号.tgz 如6.8.10 https://helm.elastic.co/helm/elasticsearch/elasticsearch-6.8.10.tgz 修改配置 修改value.yaml文件…...

WAF绕过,网络层面后门分析,Windows/linux/数据库提权实验
一、WAF绕过文件上传漏洞 win7:10.0.0.168 思路:要想要绕过WAF,第一步是要根据上传的内容找出来被拦截的原因。对于文件上传有三个可以考虑的点:文件后缀名,文件内容,文件类型。 第二步是根据找出来的拦截原…...
Oracle杀进程注意事项
文章目录 一、哪些后台进程杀死会导致数据库重启二、杀死哪些后台进程会导致数据库关闭三、杀死哪些后台进程对数据库没有影响 一、哪些后台进程杀死会导致数据库重启 CKPT:检查点进程,checkpoint 检查点,检查点事件的责任是:标志…...

Vue 3 弹出式计算器组件(源码 + 教程)
🧮 Vue 3 弹出式计算器组件(源码 教程) 📌 建议收藏 点赞 关注,本组件支持加减乘除、双向绑定、计算过程展示,适用于表单辅助输入场景。 🔧 一、完整源码(复制即用) …...

监测预警系统重塑隧道安全新范式
在崇山峻岭的脉络间延伸的隧道,曾是交通安全的薄弱环节。智慧隧道监测预警系统的诞生,正在彻底改变这种被动防御格局,通过数字神经网络的构建,为地下交通动脉注入智能守护基因。 一、安全防控体系的质变升级 1.风险感知维度革命…...
solidity中sar和>>的区别
sar和>>都是右移操作,其区别简而言之前者保留符号位,后者不保留。要解释清楚这个问题,需要从有符号数和无符号数讲起: 有符号数和无符号数 打个比方int8和uint8 uint8(无符号 8 位整数) 取值范围:…...
ESP32与STM32
ESP32与STM32深度对比:物联网与嵌入式开发的王者之争 一、核心架构对比 1.1 ESP32 - 无线物联网霸主 // 典型双核架构配置 #include "freertos/FreeRTOS.h" #include "freertos/task.h"void app_main() {// 核心0执行无线通信任务xTaskCreat…...
vue在打包的时候能不能固定assets里的js和css文件名称
在 Vue 项目中(特别是使用 Vue CLI 构建的项目),打包时生成的 assets 目录下的 .js 和 .css 文件默认会带有哈希值(如 app.123abc.js),这是为了缓存优化。但你可以配置固定名称,方法如下&#x…...
用设计模式重新思考(类FSM)验证:从混乱到优雅
在数字设计的世界里,Finite-State Machine(FSM)就像一个城市的交通信号系统。每个状态都有自己的规则,每个转换都需要精确的条件。而对于验证工程师来说,如何优雅地验证这些状态机,一直是个让人头疼的问题。…...

技巧小结:外部总线访问FPGA寄存器
概述 需求:stm32的fsmc总线挂载fpga,stm32需要访问fpga内部寄存器 1、分散加载文件将变量存放到指定地址即FPGA寄存器地址 sct文件指定变量存储地址,从而可以直接访问外设,(28335也可以,不过用的是cmd文件…...
Qt客户端技巧 -- 窗口美化 -- 圆角窗口
不解析,直接给代码例子 利用窗口重绘事件处理函数paintEvent main.cpp #include <QtCore/qglobal.h> #if QT_VERSION > 0x050000 #include <QtWidgets/QApplication> #else #include <QtGui/QApplication> #endif#include "roundedwin…...
Go语言爬虫系列教程5:HTML解析技术以及第三方库选择
Go语言爬虫系列教程5:HTML解析技术以及第三方库选择 在上一章中,我们使用正则表达式提取网页内容,但这种方法有局限性。对于复杂的HTML结构,我们需要使用专门的HTML解析库。在这一章中,我们将介绍HTML解析技术以及如何…...
理解JavaScript中map和parseInt的陷阱:一个常见的面试题解析
前言 在JavaScript面试中,map和parseInt的组合常常被用作考察候选人对这两个方法理解深度的题目。让我们通过一个简单的例子来深入探讨其中的原理。 问题现象 [1, 2, 3].map(parseInt) // 输出结果是什么?很多人可能会预期输出[1, 2, 3],但…...
文件上传漏洞深度解析:检测与绕过技术矩阵
文件上传漏洞深度解析:检测与绕过技术矩阵 引言:无处不在的文件上传风险 在当今的Web应用生态系统中,文件上传功能几乎无处不在。从社交媒体分享图片到企业文档管理系统,用户上传文件已成为现代Web应用的核心功能之一。然而&…...
3.2 HarmonyOS NEXT跨设备任务调度与协同实战:算力分配、音视频协同与智能家居联动
HarmonyOS NEXT跨设备任务调度与协同实战:算力分配、音视频协同与智能家居联动 在万物互联的全场景时代,设备间的高效协同是释放分布式系统潜力的关键。HarmonyOS NEXT通过分布式任务调度技术,实现了跨设备算力动态分配与任务无缝流转&#…...
Elasticsearch 海量数据写入与高效文本检索实践指南
Elasticsearch 海量数据写入与高效文本检索实践指南 一、引言 在大数据时代,企业和组织面临着海量数据的存储与检索需求。Elasticsearch(以下简称 ES)作为一款基于 Lucene 的分布式搜索和分析引擎,凭借其高可扩展性、实时搜索和…...

jenkins集成gitlab发布到远程服务器
jenkins集成gitlab发布到远程服务器 前面我们讲了通过创建maven项目部署在jenkins本地服务器,这次实验我们将部署在远程服务器,再以nginx作为前端项目做一个小小的举例 1、部署nginx服务 [rootweb ~]# docker pull nginx [rootweb ~]# docker images …...
AI问答-vue3+ts+vite:http://www.abc.com:3022/m-abc-pc/#/snow 这样的项目 在服务器怎么部署
为什么记录有子路径项目的部署,因为,通过子路径可以区分项目,那么也就可以实现微前端架构,并且具有独特优势,每个项目都是绝对隔离的。 要将 Vue3 项目(如路径为 http://www.abc.com:3022/m-saas-pc/#/sno…...

当主观认知遇上机器逻辑:减少大模型工程化中的“主观性”模糊
一、人类与机器的认知差异 当自动驾驶汽车遇到紧急情况需要做出选择时,人类的决策往往充满矛盾:有人会优先保护儿童和老人,有人坚持"不主动变道"的操作原则。这种差异背后,体现着人类特有的情感判断与价值选择。而机器的…...

会计 - 金融负债和权益工具
一、金融负债和权益工具区分的基本原则 (1)是否存在无条件地避免交付现金或其他金融资产的合同义务 如果企业不能无条件地避免以交付现金或其他金融资产来履行一项合同义务,则该合同义务符合金融负债的义务。 常见的该类合同义务情形包括:- 不能无条件避免的赎回; -强制…...
.net Span类型和Memory类型
.NET 中 Span 类型和 Memory 类型的深度剖析 在 .NET 编程的世界里,高效处理内存是提升程序性能的关键。Span<T> 和 Memory<T> 类型的出现,为开发者提供了强大而灵活的工具,用于高效地访问和操作连续内存区域。今天,…...

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