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

LeetCode 面试经典 150 题:删除有序数组中的重复项 II(最多保留 2 次 + 通用 k 次解法详解)

在 “删除有序数组中的重复项” 基础题之上,“最多保留指定次数重复元素” 是更贴近面试场景的延伸题。这道题不仅考察对数组 “原地操作” 的熟练度,更能体现对 “双指针思想” 的灵活应用 —— 从 “最多保留 1 次” 到 “最多保留 2 次”,再到 “最多保留 k 次”,核心逻辑可无缝迁移。本文将从题目解读、基础解法(最多保留 2 次)、通用解法(最多保留 k 次)三个维度展开,帮你彻底掌握这类问题的解题模板。

一、题目链接与题干解读

首先,你可以通过以下链接直接访问题目,先自行思考解题方向:

LeetCode 题目链接80.删除有序数组中的重复项Ⅱ

题干核心信息

题目要求如下:

给你一个非严格递增排列的数组 nums,请你原地删除重复出现的元素,使每个元素最多出现两次,返回删除后数组的新长度。元素的相对顺序应该保持一致

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 len 个元素,那么 nums 的前 len 个元素应该保存最终结果,且这些元素的相对顺序和原数组一致。

不需要考虑数组中超出新长度后面的元素。

示例理解

通过两个典型示例,能更直观地理解题目要求:

  • 示例 1:输入 nums = [1,1,1,2,2,3],输出 5,且删除后数组前 5 个元素为 [1,1,2,2,3]。解释:元素 “1” 出现 3 次,需删除 1 次,保留 2 次;“2” 出现 2 次,保留;“3” 出现 1 次,保留,最终新长度为 5。
  • 示例 2:输入 nums = [0,0,1,1,1,1,2,3,3],输出 7,且删除后数组前 7 个元素为 [0,0,1,1,2,3,3]。解释:元素 “1” 出现 4 次,需删除 2 次,保留 2 次;其他元素均满足 “最多出现 2 次”,最终新长度为 7。

二、基础解法:最多保留 2 次重复元素

由于数组是有序的,重复元素必然相邻。要实现 “每个元素最多保留 2 次”,核心思路是:用变量k记录已处理的 “符合要求数组” 的长度,遍历数组时,判断当前元素是否与已处理数组的 “倒数第 2 个元素” 相同 —— 若不同,则保留;若相同,则跳过(因为已保留 2 次,再加入会超出限制)

1. 变量k的核心作用

变量k依然扮演两个关键角色:

  1. 已处理数组的长度:k的值代表当前已筛选出的 “每个元素最多出现 2 次” 的有效元素个数;
  1. 有效元素的存放位置:数组前k个位置是已处理的符合要求区域,下一个有效元素需放到nums[k]的位置。

2. 关键判断条件:k < 2 或 x != nums[k-2]

为什么用这个条件?我们分两种情况拆解:

  • 情况 1:k < 2(已处理数组长度小于 2):此时即使当前元素与已处理元素相同,也不会超出 “最多保留 2 次” 的限制(例如k=0时,加入第一个元素;k=1时,加入与第一个元素相同的元素,此时共 2 次,符合要求),因此直接保留当前元素。
  • 情况 2:k >= 2(已处理数组长度大于等于 2):此时需判断当前元素x与已处理数组的 “倒数第 2 个元素”(即nums[k-2])是否相同 —— 若x != nums[k-2],说明当前元素加入后,该元素的出现次数不会超过 2 次(例如已处理元素为[1,1],k=2,当前元素x=2,2 != 1,加入后为[1,1,2],符合要求);若x == nums[k-2],说明已处理数组的最后 2 个元素都是x,再加入x会导致出现次数变为 3 次(例如已处理元素为[1,1],x=1,1 == 1,加入后为[1,1,1],超出限制),因此跳过。

3. 步骤拆解与示例演示

以示例 1(nums = [1,1,1,2,2,3])为例,一步步拆解整个过程:

步骤 1:初始化变量k

初始时,已处理的符合要求数组为空,因此k = 0(此时nums的前k=0个元素为空,符合要求区域尚未有元素)。

步骤 2:遍历数组,筛选有效元素

遍历数组的每个元素x(从索引 0 开始),判断是否满足k < 2或x != nums[k-2]:

  • 索引 0:x = 1,k = 0 < 2 → 符合条件,将1放到nums[0],k自增 1 → k = 1;
  • 索引 1:x = 1,k = 1 < 2 → 符合条件,将1放到nums[1],k自增 1 → k = 2;
  • 索引 2:x = 1,k = 2 >= 2,nums[k-2] = nums[0] = 1 → x == nums[k-2](超出 2 次限制),跳过,k保持 2;
  • 索引 3:x = 2,k = 2 >= 2,nums[k-2] = nums[0] = 1 → x != nums[k-2](符合要求),将2放到nums[2](此时nums变为[1,1,2,2,2,3]),k自增 1 → k = 3;
  • 索引 4:x = 2,k = 3 >= 2,nums[k-2] = nums[1] = 1 → x != nums[k-2](符合要求),将2放到nums[3](此时nums变为[1,1,2,2,2,3]),k自增 1 → k = 4;
  • 索引 5:x = 3,k = 4 >= 2,nums[k-2] = nums[2] = 2 → x != nums[k-2](符合要求),将3放到nums[4](此时nums变为[1,1,2,2,3,3]),k自增 1 → k = 5。
步骤 3:返回k的值

遍历结束后,k = 5,这就是删除重复项后数组的新长度。此时nums的前 5 个元素[1,1,2,2,3]完全符合 “每个元素最多出现 2 次” 的要求,与示例预期结果一致。

三、通用解法:最多保留 k 次重复元素

从 “最多保留 2 次” 扩展到 “最多保留 k 次”,核心逻辑完全可复用 —— 只需将判断条件中的 “倒数第 2 个元素” 改为 “倒数第 k 个元素” 即可。这是因为:

  • 对于 “最多保留 k 次” 的需求,当已处理数组长度k_len >= k时,若当前元素与已处理数组的 “倒数第 k 个元素” 相同,说明该元素已保留 k 次,再加入会超出限制;若不同,则可保留。

1. 通用解题逻辑

假设需要 “每个元素最多保留 k 次”,解题步骤如下:

  1. 初始化变量k_len:k_len = 0,代表已处理的符合要求数组的长度;
  1. 遍历数组:对于每个元素x,判断是否满足k_len < k或x != nums[k_len - k]:
    • 若满足:将x放到nums[k_len]的位置,k_len自增 1;
    • 若不满足:跳过当前元素;
  1. 返回k_len:遍历结束后,k_len即为删除重复项后数组的新长度。

2. 示例验证(最多保留 3 次)

以数组nums = [1,1,1,1,2,2,2,3],k=3(最多保留 3 次)为例:

  • 遍历过程关键步骤:
    • k_len=0:x=1,k_len < 3 → 保留,k_len=1;
    • k_len=1:x=1,k_len < 3 → 保留,k_len=2;
    • k_len=2:x=1,k_len < 3 → 保留,k_len=3;
    • k_len=3:x=1,k_len >=3,nums[3-3] = nums[0] =1 → x == nums[0](超出 3 次),跳过;
    • k_len=3:x=2,nums[0] =1 → x !=1 → 保留,k_len=4;
    • 后续元素以此类推,最终k_len=7,有效元素为[1,1,1,2,2,2,3],符合 “最多保留 3 次” 的要求。

四、复杂度分析

无论是 “最多保留 2 次” 还是 “最多保留 k 次”,复杂度均保持一致:

1. 时间复杂度:O (n)

  • 我们只对数组nums进行了一次遍历,每个元素只被判断一次(是否符合保留条件),最多执行一次赋值操作(将有效元素放到nums[k_len]);
  • 遍历的总次数为数组长度n,无嵌套循环,因此时间复杂度是线性的O(n)。

2. 空间复杂度:O (1)

  • 整个过程只用到了一个额外变量k_len(或k),没有开辟新的数组、列表或其他数据结构;
  • 所有操作都在原数组上完成,额外空间的使用与数组长度n、保留次数k均无关,因此空间复杂度是常数级的O(1)。

五、代码实现

以下以 Python,Java 为例(其他语言如 Java、C++ 逻辑一致,只需调整语法):

1,Python

class Solution:def removeDuplicates(self, nums: List[int]) -> int:k = 0for x in nums:if k < 2 or x != nums[k - 2]:nums[k] = xk += 1return k

2,Java

class Solution {public int removeDuplicates(int[] nums) {int k = 0;for (int x : nums) {if (k < 2 || x != nums[k - 2]) {nums[k++] = x;}}return k;}
}

你可以将上述代码复制到 LeetCode 编辑器中测试,例如输入nums = [1,1,1,2,2,3],代码会返回5,且nums前 5 个元素变为[1,1,2,2,3],完全符合题目要求。

六、总结与拓展

这道题的核心是 “利用数组有序性,通过判断当前元素与已处理数组‘倒数第 k 个元素’的关系,控制重复元素的保留次数”,本质仍是快慢双指针的应用 ——k_len(慢指针)维护符合要求的区域,遍历数组的元素(快指针)筛选有效元素,两者配合实现原地操作。

面试常见变形

在面试中,这类题目可能会有以下变形,但其核心逻辑不变:

  • 要求 “每个元素最多保留 3 次”:直接使用通用解法,传入k=3;
  • 要求 “删除所有重复元素,只保留不重复的元素”:本质是 “最多保留 1 次”,复用 LeetCode 26 题逻辑;
  • 无序数组的 “最多保留 k 次”:需先对数组排序(时间复杂度变为O(n log n)),再套用通用解法(但需注意排序会改变元素相对顺序,需确认题目是否允许)。

掌握 “判断当前元素与已处理数组倒数第 k 个元素关系” 的核心逻辑,能帮你轻松应对所有 “原地控制重复元素保留次数” 的数组问题,形成解题模板,提升面试效率。

希望通过本文的讲解,你能不仅学会 “删除有序数组中的重复项 II” 的解法,更能掌握通用化的解题思路,做到举一反三,应对更多类似的算法题目。


文章转载自:

http://ltocMrUF.pwdmz.cn
http://8olYchhP.pwdmz.cn
http://1CZh32q2.pwdmz.cn
http://p6oS6LVE.pwdmz.cn
http://qBy968jG.pwdmz.cn
http://2dDuTwHp.pwdmz.cn
http://spmLgnCr.pwdmz.cn
http://W8xwhoL3.pwdmz.cn
http://HNnqm0hP.pwdmz.cn
http://mXH0M0OD.pwdmz.cn
http://RWG7XCVg.pwdmz.cn
http://pm8Q7avQ.pwdmz.cn
http://SFeGxfSk.pwdmz.cn
http://SRrrcxU0.pwdmz.cn
http://mW2L5UMJ.pwdmz.cn
http://wHkScNgf.pwdmz.cn
http://oHBHLlJ8.pwdmz.cn
http://CDBs2ozZ.pwdmz.cn
http://nrr4GouL.pwdmz.cn
http://zC6ecCp9.pwdmz.cn
http://5hb0Q3WX.pwdmz.cn
http://ZmavRIXl.pwdmz.cn
http://dW9S9Hg6.pwdmz.cn
http://C2YibXjx.pwdmz.cn
http://znxE5RZS.pwdmz.cn
http://IPbehcpF.pwdmz.cn
http://izwokebv.pwdmz.cn
http://hKaAagyX.pwdmz.cn
http://yRJH8oCo.pwdmz.cn
http://WrlIfpXC.pwdmz.cn
http://www.dtcms.com/a/374181.html

相关文章:

  • 在OpenHarmony上适配图形显示【2】——调试display hdi的技巧
  • 在 JavaScript 中轻松实现 AES 加密与解密:从原理到实战
  • Mockoon:开源免费的本地Mock服务工具,提升前后端联调效率
  • C/C++圣诞树②
  • segYolo添加界面
  • 初学Transformer核心——注意力机制
  • 第9篇:Freqtrade量化交易之config.json 基础入门与初始化
  • 推荐系统学习笔记(十六)LHUC(PPNet)
  • 前端开发实战 主流前端开发工具对比与最佳实践
  • 淘宝 API 技术架构与实战指南:从实时数据流到 AIGC 融合的电商开发新范式
  • 基于AD9689BBPZ-2600 的高速数字采集 板卡
  • Transformer 模型:Attention is All You Need 的真正含义
  • BUU MISC(看心情写)
  • 第三方网站数据库测评:【源码级SQL注入与数据泄露风险全面测评】
  • 【Linux基础】parted命令详解:从入门到精通的磁盘分区管理完全指南
  • 实践《数字图像处理》之Canny边缘检测、霍夫变换与主动二值化处理在短线段清除应用中的实践
  • sim2real_动作迁移常用的方法和思路(比如bvh->robot)
  • 第六届机器学习与计算机应用国际学术会议
  • 正交匹配追踪(OMP)详解:压缩感知的基石算法
  • Github项目推荐:Made-With-ML 机器学习工程学习指南
  • 【Java实战㉞】从0到1:Spring Boot Web开发与接口设计实战
  • Python从入门到精通_01_python基础
  • 基于开源做的图片压缩工具
  • 联邦学习与大模型结合
  • SQL隐式链接显式连接
  • pd19虚拟机安装Win11系统
  • 【面试场景题】如何进行高并发系统的性能测试?
  • Keepalived配置好后,域名解析到哪里
  • 滑动窗口题目:长度最小的子数组
  • 如何Maven 构建问题排查与依赖管理