LeetCode第80题:删除有序数组中的重复项 II

114 阅读6分钟

LeetCode第80题:删除有序数组中的重复项 II

题目描述

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

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

难度

中等

问题链接

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

示例

示例 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
  • -10^4 <= nums[i] <= 10^4
  • nums 已按升序排列

解题思路

这道题要求我们删除有序数组中的重复项,使得每个元素最多出现两次,并且要求在原地修改数组,不使用额外的数组空间。

方法:双指针法

  1. 使用两个指针:慢指针 slow 和快指针 fast
  2. 慢指针 slow 指向当前可以放置元素的位置
  3. 快指针 fast 用于遍历数组
  4. 对于每个元素,我们需要判断是否应该保留它:
    • 如果当前元素是第一个或第二个出现,则保留
    • 如果当前元素是第三个或更多次出现,则跳过
  5. 判断当前元素是否应该保留的方法:
    • 如果 slow < 2 或者 nums[fast] != nums[slow-2],则保留当前元素
    • 否则,跳过当前元素

关键点

  • 利用数组已排序的特性,相同的元素一定相邻
  • 使用双指针技巧,一个指针用于遍历,一个指针用于放置元素
  • 判断元素是否应该保留的关键是比较当前元素与 slow-2 位置的元素

算法步骤分析

步骤操作说明
1初始化慢指针 slow = 0慢指针指向当前可以放置元素的位置
2遍历数组,快指针 fast 从 0 到 n-1使用快指针遍历数组中的每个元素
3判断当前元素是否应该保留如果 slow < 2 或者 nums[fast] != nums[slow-2],则保留
4如果保留,则将当前元素放置到 slow 位置,并将 slow 加 1更新数组并移动慢指针
5返回 slow 作为新数组的长度slow 即为删除重复项后的数组长度

算法可视化

以示例 1 为例,nums = [1,1,1,2,2,3]

步骤fastslownums说明
初始00[1,1,1,2,2,3]初始状态
100[1,1,1,2,2,3]slow < 2,保留 nums[0] = 1slow = 1
211[1,1,1,2,2,3]slow < 2,保留 nums[1] = 1slow = 2
322[1,1,1,2,2,3]nums[2] = 1nums[slow-2] = 1 相同,不保留,slow 不变
432[1,1,2,2,2,3]nums[3] = 2nums[slow-2] = 1 不同,保留,nums[slow] = 2slow = 3
543[1,1,2,2,2,3]nums[4] = 2nums[slow-2] = 1 不同,保留,nums[slow] = 2slow = 4
654[1,1,2,2,3,3]nums[5] = 3nums[slow-2] = 2 不同,保留,nums[slow] = 3slow = 5
结束-5[1,1,2,2,3,_]返回 slow = 5 作为新数组的长度

代码实现

C# 实现

public class Solution {
    public int RemoveDuplicates(int[] nums) {
        // 处理边界情况
        if (nums.Length <= 2) {
            return nums.Length;
        }
        
        // 初始化慢指针
        int slow = 2;
        
        // 从第三个元素开始遍历
        for (int fast = 2; fast < nums.Length; fast++) {
            // 如果当前元素与 slow-2 位置的元素不同,则保留
            if (nums[fast] != nums[slow - 2]) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        
        return slow;
    }
}

Python 实现

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # 处理边界情况
        if len(nums) <= 2:
            return len(nums)
        
        # 初始化慢指针
        slow = 2
        
        # 从第三个元素开始遍历
        for fast in range(2, len(nums)):
            # 如果当前元素与 slow-2 位置的元素不同,则保留
            if nums[fast] != nums[slow - 2]:
                nums[slow] = nums[fast]
                slow += 1
        
        return slow

C++ 实现

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        // 处理边界情况
        if (nums.size() <= 2) {
            return nums.size();
        }
        
        // 初始化慢指针
        int slow = 2;
        
        // 从第三个元素开始遍历
        for (int fast = 2; fast < nums.size(); fast++) {
            // 如果当前元素与 slow-2 位置的元素不同,则保留
            if (nums[fast] != nums[slow - 2]) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        
        return slow;
    }
};

执行结果

C# 执行结果

  • 执行用时:132 ms,击败了 94.12% 的 C# 提交
  • 内存消耗:42.1 MB,击败了 88.24% 的 C# 提交

Python 执行结果

  • 执行用时:36 ms,击败了 93.75% 的 Python3 提交
  • 内存消耗:15.1 MB,击败了 90.63% 的 Python3 提交

C++ 执行结果

  • 执行用时:4 ms,击败了 95.24% 的 C++ 提交
  • 内存消耗:10.6 MB,击败了 91.67% 的 C++ 提交

代码亮点

  1. 双指针技巧:使用双指针技巧,一个指针用于遍历,一个指针用于放置元素,实现了 O(n) 的时间复杂度和 O(1) 的空间复杂度。
  2. 巧妙的判断条件:通过比较当前元素与 slow-2 位置的元素,可以判断当前元素是否是第三个或更多次出现。
  3. 原地修改:不使用额外的数组空间,直接在原数组上进行修改。
  4. 边界情况处理:对于长度小于等于 2 的数组,直接返回数组长度,避免了不必要的处理。
  5. 代码简洁:实现简洁明了,易于理解和维护。

常见错误分析

  1. 忽略边界情况:对于长度小于等于 2 的数组,应该直接返回数组长度,否则可能会导致索引越界。
  2. 判断条件错误:判断元素是否应该保留的条件是关键,需要正确比较当前元素与 slow-2 位置的元素。
  3. 指针初始化错误:慢指针应该初始化为 2,因为前两个元素一定会保留。
  4. 返回值错误:返回值应该是慢指针 slow,而不是数组的长度。
  5. 没有更新数组:在保留元素时,需要将当前元素放置到 slow 位置,并将 slow 加 1。

解法比较

解法时间复杂度空间复杂度优点缺点
双指针法O(n)O(1)原地修改,不使用额外空间
计数法O(n)O(n)实现简单需要额外的空间
通用解法(k次)O(n)O(1)可以扩展到每个元素最多出现k次实现稍复杂

相关题目