【详细解析】删除有序数组中的重复项 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)。

相关推荐
AI成长日志7 分钟前
【GitHub开源项目专栏】黑客松项目架构模式解析:微服务、事件驱动与Serverless实战
算法
人道领域8 分钟前
【LeetCode刷题日记:24】两两交换链表
算法·leetcode·链表
北顾笙98011 分钟前
day16-数据结构力扣
数据结构·算法·leetcode
AI成长日志23 分钟前
【算法学习专栏】动态规划基础·简单三题精讲(70.爬楼梯、118.杨辉三角、121.买卖股票的最佳时机)
学习·算法·动态规划
wsoz25 分钟前
Leetcode子串-day4
c++·算法·leetcode
汀、人工智能44 分钟前
[特殊字符] 第27课:环形链表II
数据结构·算法·链表·数据库架构··环形链表ii
会编程的土豆44 分钟前
【数据结构与算法】二叉树大总结
数据结构·算法·leetcode
沉鱼.441 小时前
第十届题目
算法
y = xⁿ1 小时前
【LeetCode Hot100】动态规划:T70:爬楼梯 T118:杨辉三角形 T198:打家劫舍
算法·leetcode·动态规划
Liangwei Lin1 小时前
洛谷 P1460 [USACO2.1] 健康的荷斯坦奶牛 Healthy Holsteins
数据结构·算法