【详细解析】删除有序数组中的重复项 II

一、问题描述

1. 核心要求

给定一个升序排列 的数组 nums,需要原地删除重复出现的元素,使得每个元素最多出现两次,返回删除后数组的新长度。

  • 空间复杂度要求:O(1)(不能使用额外数组);

  • 输入数组以"引用"传递,修改需对调用者可见;

  • 无需处理新长度之外的数组元素。

2. 示例分析

输入数组 输出长度 输出数组(前N位) 说明
[1,1,1,2,2,3] 5 [1,1,2,2,3] 数字1重复3次,保留2次
[0,0,1,1,1,1,2,3,3] 7 [0,0,1,1,2,3,3] 数字1重复4次,保留2次

3. 关键约束

  • 数组长度范围:1 ≤ nums.length ≤ 3×10⁴;

  • 数组元素范围:-10⁴ ≤ nums[i] ≤ 10⁴;

  • 数组已按升序排列(核心前提,可利用有序性简化逻辑)。

二、解题思路

1. 核心逻辑:双指针法

由于数组是有序的,重复元素必然连续出现,因此可以用快慢指针实现原地修改:

  • 慢指针(slow):标记新数组的末尾位置(即最终保留的元素的最后一位);

  • 快指针(fast):遍历原数组,检查当前元素是否符合"最多出现两次"的条件。

2. 条件判断规则

对于快指针指向的元素 nums[fast],只需判断:

nums[fast] 是否与慢指针前两位的元素 nums[slow-2] 相等。

  • 若不相等:说明当前元素可保留,将其赋值给 nums[slow],慢指针右移;

  • 若相等:说明当前元素已出现两次以上,跳过该元素,快指针继续右移。

3. 边界处理

  • 当数组长度 ≤ 2 时,无需删除任何元素,直接返回原长度;

  • 慢指针初始值为 2(前两位元素必然保留),快指针从 2 开始遍历。

三、完整代码实现

Python 复制代码
from typing import List

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # 边界条件:数组长度≤2时,直接返回原长度
        if len(nums) <= 2:
            return len(nums)
        
        # 慢指针:初始为2(前两位必然保留)
        slow = 2
        # 快指针:从2开始遍历数组
        for fast in range(2, len(nums)):
            # 核心判断:当前元素与慢指针前两位元素不同,说明可保留
            if nums[fast] != nums[slow - 2]:
                nums[slow] = nums[fast]
                slow += 1
        
        # 慢指针的最终位置即为新数组长度
        return slow

四、代码核心解析

1. 边界条件处理

Python 复制代码
if len(nums) <= 2:
    return len(nums)
  • 当数组长度≤2时,无论元素是否重复,都满足"最多出现两次"的要求,直接返回原长度,避免无效计算。

2. 双指针初始化

Python 复制代码
slow = 2
for fast in range(2, len(nums)):
  • 慢指针 slow 初始化为 2:前两位元素(索引0、1)必然保留,无需检查;

  • 快指针 fast 从 2 开始遍历:只需检查第三位及以后的元素。

3. 核心判断与赋值

Python 复制代码
if nums[fast] != nums[slow - 2]:
    nums[slow] = nums[fast]
    slow += 1
  • 关键逻辑:利用数组的有序性,只需对比当前元素与新数组倒数第二位(slow-2)是否相等,即可判断是否超过两次;

  • 赋值操作:将符合条件的元素"覆盖"到慢指针位置,实现原地修改;

  • 慢指针右移:标记新数组的下一个空位。

4. 返回值

Python 复制代码
return slow
  • 慢指针的最终位置即为新数组的长度(因为慢指针始终指向新数组的下一个空位)。

五、测试用例验证

测试用例1:nums = [1,1,1,2,2,3]

遍历步骤 fast nums[fast] nums[slow-2] 条件判断 slow nums 数组变化
初始 - - - - 2 [1,1,1,2,2,3]
1 2 1 nums[0]=1 相等 2 无变化
2 3 2 nums[0]=1 不相等 3 [1,1,2,2,2,3]
3 4 2 nums[1]=1 不相等 4 [1,1,2,2,2,3]
4 5 3 nums[2]=2 不相等 5 [1,1,2,2,3,3]
  • 最终返回 slow=5,数组前5位为 [1,1,2,2,3],符合示例要求。

测试用例2:nums = [0,0,1,1,1,1,2,3,3]

遍历步骤 fast nums[fast] nums[slow-2] 条件判断 slow nums 数组变化
初始 - - - - 2 [0,0,1,1,1,1,2,3,3]
1 2 1 nums[0]=0 不相等 3 [0,0,1,1,1,1,2,3,3]
2 3 1 nums[1]=0 不相等 4 [0,0,1,1,1,1,2,3,3]
3 4 1 nums[2]=1 相等 4 无变化
4 5 1 nums[2]=1 相等 4 无变化
5 6 2 nums[2]=1 不相等 5 [0,0,1,1,2,1,2,3,3]
6 7 3 nums[3]=1 不相等 6 [0,0,1,1,2,3,2,3,3]
7 8 3 nums[4]=2 不相等 7 [0,0,1,1,2,3,3,3,3]
  • 最终返回 slow=7,数组前7位为 [0,0,1,1,2,3,3],符合示例要求。

测试用例3:nums = [1,2,3]

  • 数组长度=3,slow初始为2,fast遍历2:

    • nums[2]=3 ≠ nums[0]=1 → slow=3;
  • 返回3,数组无变化,符合预期。

六、复杂度分析

时间复杂度

  • 仅遍历数组一次(fast指针从2到len(nums)-1),时间复杂度为 O(n)(n为数组长度);

  • 所有操作均为常数级,无嵌套循环,效率最优。

空间复杂度

  • 仅使用了slow、fast两个指针变量,无额外数组或容器,空间复杂度为 O(1),满足题目要求。

七、总结

关键点回顾

  1. 双指针核心:慢指针标记新数组边界,快指针遍历检查,利用有序性简化重复判断;

  2. 条件简化 :无需统计元素出现次数,只需对比当前元素与新数组倒数第二位(slow-2),即可判断是否超过两次;

  3. 原地修改:通过覆盖赋值实现O(1)空间复杂度,符合题目核心要求;

  4. 边界处理:提前处理数组长度≤2的情况,避免逻辑冗余。

该方法是解决"有序数组去重"类问题的经典思路,通过优化判断条件,既保证了逻辑简洁性,又实现了最优的时间和空间复杂度,可推广到"保留k次重复"的通用场景(只需将判断条件中的slow-2改为slow-k)。

相关推荐
sg_knight1 小时前
OpenClaw 能做什么?几个真实使用场景说明
算法·ai·大模型·llm·agent·openclaw·小龙虾
嫂子开门我是_我哥2 小时前
心电域泛化研究从0入门系列 | 第七篇:全流程闭环与落地总结——系列终篇
人工智能·算法·机器学习
爱学习的小囧2 小时前
零门槛!VCF 自动化环境登录 vSphere Supervisor 全教程
运维·服务器·算法·自动化·vmware·虚拟化
Book思议-2 小时前
线性表之顺序表入门:顺序表从原理到实现「增删改查」
数据结构·算法
I_LPL2 小时前
day52 代码随想录算法训练营 图论专题6
java·数据结构·算法·图论
lxl13072 小时前
C++算法(11)字符串
开发语言·c++·算法
passxgx2 小时前
12.3 多维高斯分布与加权最小二乘法
线性代数·算法·最小二乘法
少许极端2 小时前
算法奇妙屋(三十)-递归、回溯与剪枝的综合问题 3
算法·深度优先·剪枝·数独·n皇后
WBluuue2 小时前
数据结构与算法:01分数规划
c++·算法