算法笔记4:删除重复项II

题目

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

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

示例 1:输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 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]
解释:函数应返回新长度 length = 7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。

题解

这道题是上一道题目删除重复项的一个变体,要求我们删除重复项,但要将两个以上元素的保留至两个。相信大部分朋友的第一反应应该都是添加一个计数器,在上一道题的代码上进行一些小的修改,使得能够保留两个相同值,符合题意。

方法一:双指针计数

既然上一道题目中我们已经有了一个很完备的方法操作双指针进行扫描,这里需要留下两个应该也不是一件难事,我们只需要添加一个布尔变量,用于标记我们是否已经遇见过一个相同的元素了,第一次我们将他留下来,此时已有两个元素相同,第二次时,我们将第三个或以上的元素覆盖掉,将他们舍弃。看代码:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n = nums.size();
        bool twice = false;

        int fast = 1, slow = 1;
        while (fast < n) {
            if (nums[fast] != nums[fast - 1]) {
                nums[slow] = nums[fast];
                ++slow;
                ++fast;
                twice = false;
            }
            else if (!twice){
                nums[slow] = nums[fast];
                ++slow;
                ++fast;
                twice = true;
            }
            else{
                 ++fast; 
            }
  
        }
        return slow;
    }
};

在这段代码中,我们引入了twice这个布尔变量,其余部分和上一道题目很是相似。进入循环之后,我们首先判断fast指针指向的元素和其的左边元素是否相同,如若不同,我们就将slow指针这里的元素附上fast指向的元素,左右指针各自前进一步,将twice判定置为否。如果fast指针指向的元素和他左边的元素相同,那么我们就要检查twice的值,如果twice的值为否,说明我们上一次遇到的是一个不同元素,此时是我们刚刚遇到相同的元素,我们可以再容忍一个相同元素的写入,因此,我们的操作和见到不同元素一样,将fast的值写入slow,左右指针各自前进一步,此时我们将twice置为是,提醒循环,我们已经接受了一个相同的元素,不要再放下一个进来了,那么下一个元素再次相同时,我们会进入最后一段else的操作,直接右移fast指针,直到其遇到不同元素将twice再一次置为否时才能进入容忍模式。至此,遍历至数组末尾,方法一结束。

方法二:步长双指针

有没有不需要布尔变量判断的方法呢?答案是肯定的。因为给定数组是有序的,所以相同元素必然连续。我们可以使用另一种双指针解决本题,遍历数组检查每一个元素是否应该被保留,如果应该被保留,就将其移动到指定位置。

因为本题要求相同元素最多出现两次而非一次,所以我们需要检查上上个应该被保留的元素 nums[slow−2]是否和当前待检查元素 nums[fast]相同。当且仅当 nums[slow−2]=nums[fast] 时,当前待检查元素 nums[fast]不应该被保留(因为此时必然有 nums[slow−2]=nums[slow−1]=nums[fast])。最后,slow即为处理好的数组的长度。

特别地,数组的前两个数必然可以被保留,因此对于长度不超过 2 的数组,我们无需进行任何处理,对于长度超过 2 的数组,我们直接将双指针的初始值设为 2 即可。看看代码:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n = nums.size();
        if (n <= 2) {
            return n;
        }
        int slow = 2, fast = 2;
        while (fast < n) {
            if (nums[slow - 2] != nums[fast]) {
                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }
        return slow;
    }
};

在这个方法中,最重要的地方在于,题目给出的数组已经是按照升序排序好的,那么相同的几个元素必然是连续的,所以我们只需要设定一个步长,这里的步长为2,如果我迈了一步,发现踩到的仍然是相同的元素,那这一步之中必然都是相同的元素,所以我们只需要保留步内的元素,舍弃后续的元素即可。同样地,如果我们需要保留三个相同的元素,那么我们可以设置步长为3,就可以简单的改变程序使得留下最多三个重复的元素了。

写在最后

这道题目给予了一些代码泛用性的启示,我们可以通过复用以往的工作减少工作量,亦或是创造更易于扩展与修改的代码方便未来的使用。

Breeze Wang

A student majoring in Software Engineering at Central South University has an understanding of software development techniques, software architecture, and is able to use Godot to develop game projects. I am currently in the Game Development Laboratory at Central South University. I have experience participating in Global Game Jam. Loving game development.