#80 双指针 中等

删除有序数组中的重复项 II

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

给你一个有序数组 nums,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

请注意,输入数组是以「引用」方式传递的:函数返回的长度 k 表示 nums 的前 k 个元素为修改后的有效结果,超出部分可忽略。

示例 1

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
函数应返回新长度 5,原数组前五个元素被修改为 1, 1, 2, 2, 3。不需要考虑超出新长度后面的元素。

示例 2

输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
函数应返回新长度 7,原数组前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。

💭 模拟答题者思考

1. 暴力想法:用哈希表统计每个数出现次数,再开新数组按序填入、每个数最多写两次——正确但 O(n) 额外空间,不符合原地 O(1) 要求。

2. 和 #26「每个数只留一次」的关系:本题是「每个数最多留两次」。#26 用 nums[fast] != nums[slow] 判断新值;这里需要判断「当前值是否还能再写一次」。

3. 有序性:相同数字在数组中连续。若结果前缀 nums[0..slow-1] 里某值已出现 2 次,则这 2 次一定占据末尾两个位置,且与即将扫描的 nums[fast] 相等时,nums[slow-2] 必然等于 nums[fast]。

4. 写入条件:前两个位置直接写(slow < 2);否则仅当 nums[fast] != nums[slow-2] 时写入——等价于「该值在前缀里还不到 2 次,或 nums[fast] 是更大的新值」。

5. 一遍扫描,每个下标常数工作,返回 slow。与 #26 同属写指针 + 读指针,只是判等对象从 nums[slow] 换成 nums[slow-2]。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
slowint定义:下一个待写入位置的下标,也是当前已保留元素个数;nums[0..slow-1] 为「每个值最多出现 2 次」的有效前缀
维护:初始可视为空前缀,扫描时按需扩展;前两个位置无条件保留(任意值最多出现两次,前两位不可能超限)
更新:当判定 nums[fast] 可写入时,执行 nums[slow] = nums[fast]; slow++
fastint定义:从左到右扫描原数组的读指针
维护:每轮用 nums[fast] 与结果区中「倒数第二个」元素比较,判断是否还能再保留一份相同值
更新:每轮循环末尾 fast++,直到遍历完整个数组
nums[slow-2](比较锚点)int定义:当 slow ≥ 2 时,有效前缀里「同一数值已出现次数是否已达 2」的判据:若 nums[fast] == nums[slow-2],则再写入会形成第 3 个连续相同值
维护:有序数组下,相同值在结果前缀中必然相邻,故看倒数第二个即可代表「该值已保留了几份」
更新:仅用于条件 slow < 2 or nums[fast] != nums[slow-2],不单独维护变量
k(返回值)int定义:去重(限 2 次)后的有效长度
维护:等于扫描结束后的 slow
更新:循环结束后直接返回 slow

⌨️ 落码步骤

1. 令 slow = 0,for fast in range(len(nums)) 扫描每个元素

2. 若 slow < 2 或 nums[fast] != nums[slow - 2],说明允许保留 nums[fast]:执行 nums[slow] = nums[fast],slow += 1

3. 否则跳过 nums[fast](该值在前缀中已有 2 份)

4. 扫描结束,nums[0..slow-1] 为符合题意的数组,返回 slow

💻 代码实现

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        slow = 0  # nums[0..slow-1] 为每个值最多出现 2 次的前缀
        for fast in range(len(nums)):
            if slow < 2 or nums[fast] != nums[slow - 2]:
                nums[slow] = nums[fast]
                slow += 1
        return slow
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int slow = 0;  // nums[0..slow-1] 为每个值最多出现 2 次的前缀
        for (int fast = 0; fast < nums.size(); fast++) {
            if (slow < 2 || nums[fast] != nums[slow - 2]) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }
};
// 时间 O(n),空间 O(1)

📈 复杂度分析

时间复杂度 O(n)
空间复杂度 O(1)

⚠️ 常见坑

❗ 误用 #26 的条件 nums[fast] != nums[slow]:那样每个数只保留 1 次,与「最多 2 次」不符。

❗ 比较下标写成 nums[slow-1]:只保证与紧邻前一个不同,仍可能写入第 3 个相同值(如 1,1,1 时会错误保留三个 1)。

❗ 返回值写成 slow - 1 或 slow + 1:本题 slow 本身就是已写入元素个数,应直接返回 slow(与 #26 的 slow+1 不同,因 #26 的 slow 是最后下标而非长度)。

🔍 必测边界 Case

Case 1:长度 ≤ 2
nums = [1] → 1;nums = [1,2] → 2(始终满足 slow < 2,全部保留)
Case 2:同一值超过 2 次
nums = [1,1,1,1,1] → 2, nums = [1,1,...](第三个及以后的 1 被跳过)
Case 3:混合重复
nums = [0,0,1,1,1,1,2,3,3] → 7(四个 1 只保留两个,与示例 2 一致)