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

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

LeetCode80这道题在LeetCode上是一道经典的数组处理题目,考察的是 双指针(Two Pointers) 技巧以及 原地算法(In-place Algorithm) 的设计能力。在NOI/NOIP竞赛中,这类题目通常作为考察思维严谨性和代码实现基础的签到题出现,或者作为复杂数据结构题的一部分。

为了适应竞赛训练,我将原题改编成了标准的OI题目格式(Standard I/O),并着重强调了空间复杂度的限制。


题目:删除有序数组中的重复项 II (Remove Duplicates from Sorted Array II)

题目描述

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

你需要满足以下严格限制:

  1. 原地修改:不要使用额外的数组空间,你必须在 O(1)O(1) 的额外空间条件下完成。
  2. 相对顺序:元素的相对顺序应该保持一致。
  3. 结果验证:你的程序需要输出处理后的新长度 len,以及数组前 len 个元素。

注意:这道题的核心不在于输出,而在于如何高效地在原数组上进行数据搬运和覆盖

输入格式

第一行包含一个整数 NN,表示数组 nums 的长度。 第二行包含 NN 个整数,表示数组 nums 的元素。保证输入的数组是非递减有序的。

输出格式

第一行输出一个整数,表示处理后的新长度 len。 第二行输出 len 个整数,表示处理后的数组前 len 个元素,整数之间用空格分隔。

样例输入 1

6
1 1 1 2 2 3

样例输出 1

5
1 1 2 2 3

样例说明:函数应该返回新的长度 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。不需要考虑数组中超出新长度后面的元素。

样例输入 2

9
0 0 1 1 1 1 2 3 3

样例输出 2

7
0 0 1 1 2 3 3

样例说明:函数应该返回新的长度 7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。

数据范围与提示

  • 1N3×1041 \le N \le 3 \times 10^4
  • 104nums[i]104-10^4 \le nums[i] \le 10^4
  • 数组严格按非递减顺序排列。
  • 空间复杂度限制:必须为 O(1)O(1),即除了几个变量外,不得开辟与 NN 相关的辅助数组(如 vector<int> temp(N) 是不允许的)。

教练的解题思路提示 (Pseudo-code)

这道题的关键在于维护两个指针:

  1. 遍历指针 (fast):用于读取数组中的每一个元素。
  2. 放置指针 (slow):指向当前有效结果数组的末尾(即下一个待填入数据的位置)。

因为题目允许元素最多出现两次,所以判断是否保留当前元素的依据是:它是否和结果数组中倒数第二个元素相同。

NOI C++14 风格伪代码提示:

/* 
   核心函数逻辑提示 
   假设数组为 A,长度为 n
*/

Function solve(A, n):
    // 1. 处理边界情况
    IF n <= 2 THEN
        RETURN n  // 长度小于等于2,无需删除,直接返回
    END IF

    // 2. 初始化双指针
    // slow 指针定义:当前“合法区域”的下一个写入位置
    // 因为前两个元素一定合法(无论是否重复),所以 slow 从 2 开始
    INTEGER slow = 2

    // 3. fast 指针从第3个元素(下标2)开始遍历整个数组
    FOR fast FROM 2 TO n - 1 DO
        // 【核心逻辑】
        // 检查 A[fast] 是否可以放入合法区域
        // 条件:A[fast] 不等于 A[slow - 2]
        // 原因:如果 A[fast] == A[slow - 2],且数组是有序的,
        // 说明 A[slow-2], A[slow-1], A[fast] 三者相同(或者更多),
        // 此时 A[fast] 是第三个重复项,需要丢弃。
        // 反之,如果不等,说明 A[fast] 最多是该数值的第1个或第2个,可以保留。
        
        IF A[fast] != A[slow - 2] THEN
            A[slow] = A[fast]   // 将 fast 指向的值搬运到 slow 位置
            slow = slow + 1     // 合法区域长度 + 1
        END IF
    END FOR

    // 4. 返回新长度
    RETURN slow

知识点总结 (Knowledge Points)

在做这道题时,你需要掌握以下核心考点:

  1. 双指针法 (Two Pointers):利用快慢指针在一个数组中同时进行读写操作,快指针负责寻找新元素,慢指针维护有效数据的边界。
  2. 有序数组特性 (Sorted Array Property):因为数组有序,重复元素必然相邻。这简化了判重逻辑,不需要哈希表。
  3. 原地操作 (In-place Operations):直接在输入数组上修改,通过覆盖旧数据来达到删除效果,理解覆盖与交换的区别。
  4. 边界条件处理:考虑数组长度小于等于 2 的情况,这是不需要处理的基准情况 (Base Case)。