面试经典题目——删除有序数组中的重复项||(难度:中等)

目录

题目链接:

题目描述

解法一:双指针

1. 问题背景

2. 核心思路

3. 实现步骤

(1) 初始条件判断

(2) 初始化指针

(3) 双指针遍历

(4) 返回结果

4. 示例解析

示例 1

示例 2

Java写法:

C++写法:

运行时间

时间复杂度和空间复杂度

​编辑

总结


题目链接:

注:下述题目描述和示例均来自力扣

题目描述

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

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

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 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。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 10^4
  • -104 <= nums[i] <= 104
  • nums 已按升序排列


解法一:双指针

1. 问题背景

  • 输入是一个有序数组 nums
  • 要求删除重复超过两次的元素,使得每个元素最多保留两次。
  • 必须在原地修改数组,并且只使用 O(1) 的额外空间。
  • 返回值是新数组的长度,同时通过原数组的前 length 个元素表示修改后的结果。

2. 核心思路

由于数组是有序的,重复的元素必然连续排列。因此,我们可以用两个指针:

  • left 指针:用于记录当前可以放置有效元素的位置。
  • right 指针:用于遍历整个数组,寻找符合条件的元素。

算法的关键在于:

  • 如果某个元素与 left - 2 位置的元素不同(即它不是第三个或更多次重复的元素),则将其放入 left 指针的位置。
  • 否则,跳过这个元素(因为它是多余的重复项)。

3. 实现步骤

(1) 初始条件判断
if(len <= 2){
    return len;
}
  • 如果数组长度小于等于 2,则直接返回数组长度,因为最多只有两个元素,不可能有重复超过两次的情况。

(2) 初始化指针
int left = 2;
int right = 2;
  • left 和 right 都初始化为 2(索引从 0 开始)。
  • 原因是:数组的前两个元素无论如何都不会被删除(最多保留两次),所以可以直接从第三个元素开始处理。

(3) 双指针遍历
while(right < len){
    if(nums[left - 2] != nums[right]){
        nums[left] = nums[right];
        left++;
    }
    right++;
}
  • right 指针:从索引 2 开始,逐一遍历数组中的所有元素。
  • left 指针:指向当前可以放置有效元素的位置。
  • 核心判断条件
    if(nums[left - 2] != nums[right])
    • 如果 nums[left - 2] 与 nums[right] 不相等,说明 nums[right] 是一个有效的元素(不是第三次或更多次重复的元素)。
    • 此时将 nums[right] 放入 nums[left] 的位置,并将 left 指针向后移动一位。
  • 否则
    • 如果 nums[left - 2] == nums[right],说明 nums[right] 是多余的重复元素,直接跳过,不进行任何操作。

(4) 返回结果
return left;
  • 最终,left 指针的位置就是新数组的有效长度,因为 left 指针始终指向下一个可以放置有效元素的位置。
  • 数组的前 left 个元素就是修改后的结果。

4. 示例解析

示例 1

输入:nums = [1,1,1,2,2,3]

执行过程:

  • 初始状态:left = 2right = 2
  • 第一次比较:nums[left - 2] = nums[0] = 1nums[right] = nums[2] = 1,相等,跳过。
  • 第二次比较:nums[left - 2] = nums[0] = 1nums[right] = nums[3] = 2,不相等,替换并移动 left
  • 结果:nums = [1,1,2,2,3,...]left = 5

输出:5, nums = [1,1,2,2,3]


示例 2

输入:nums = [0,0,1,1,1,1,2,3,3]

执行过程:

  • 初始状态:left = 2right = 2
  • 第一次比较:nums[left - 2] = nums[0] = 0nums[right] = nums[2] = 1,不相等,替换并移动 left
  • 第二次比较:nums[left - 2] = nums[1] = 0nums[right] = nums[3] = 1,不相等,替换并移动 left
  • 第三次比较:nums[left - 2] = nums[2] = 1nums[right] = nums[4] = 1,相等,跳过。
  • ...
  • 结果:nums = [0,0,1,1,2,3,3,...]left = 7

输出:7, nums = [0,0,1,1,2,3,3]


    Java写法:

    class Solution {
        public int removeDuplicates(int[] nums) {
            // 有序数组
            int len = nums.length;
            // 如果本来就不超过两个那就直接返回答案
            if(len <= 2){
                return len;
            }
    
            // 直接从索引2也就是第三个位置开始判断
            // right用于寻找不同的值
            // left用于记录替换的位置
            // left - 2的位置用于跟right比较
            int left = 2;
            int right = 2;
            while(right < len){
                // 看是否是同一元素
                if(nums[left - 2] != nums[right]){
                    // 不是,替换并且更新替换的位置
                    nums[left] = nums[right];
                    left++;
                }
                // right指针一直寻找
                right++;
            }
            // 最后left 的位置就最终的索引停留的位置的后面,因为他最后又执行了一次+1操作
            return left;
        }
    }

    C++写法:

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

    运行时间

    时间复杂度和空间复杂度

    • 时间复杂度:O(n),其中 n 是数组的长度。right 指针遍历整个数组一次。
    • 空间复杂度:O(1),只使用了常数级别的额外空间。



    总结

            我嘞个双指针的操作啊,其实这个题都是这样的模版,数组的这种操作还是太简单了。