leetcode80:删除有序数组中的重复项 II(快慢指针法)

新星杯·14天创作挑战营·第14期 10w+人浏览 110人参与


LeetCode 80 删除有序数组中的重复项 II,【难度:中等;通过率:63.5%】,这道题是 LeetCode 26 删除有序数组中的重复项 的进阶版,要求我们保留最多 两个重复的元素,同样可是接着使用 双指针-快慢指针法解决,并且我们通过此题,总结出一个 通用的模板,最终可以解决 删除有序数组中的重复 K 项的问题

一、 题目描述

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

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

示例:

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3,_]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3

二、 核心思路:快慢指针

解决此类问题的核心武器是快慢指针。我们将数组逻辑上分为两部分:

  • [0...slow-1]:这是我们处理好的、满足“最多重复两次”要求的新数组
  • [slow...fast-1]:这是已经被 fast 指针扫描过,但被我们“抛弃”的元素
  • [fast...n-1]:这是待探索的未知区域

指针职责:

  • slow (慢指针):作为“写入”指针,nums[slow] 是下一个将被覆盖的位置。slow 本身也代表了新数组的长度
  • fast (快指针):作为“读取”指针,nums[fast] 是当前正在考察的元素

我们的目标就是移动 fast 指针遍历整个数组,当遇到一个“合法”的元素时,就将它“写入”到 slow 指针的位置,然后 slow 前进一格


三、计数器辅助的快慢指针

一种非常直观的实现思路:通过一个额外的计数器来判断当前元素是否“合法”

代码实现-直观快慢指针

class Solution {
    /**
     * 使用快慢指针,并辅以一个 repeat 计数器来跟踪当前元素的重复次数
     * slow 指针永远指向下一个可以被覆盖的位置
     */
    public int removeDuplicates(int[] nums) {
        if (nums.length <= 2) {
            return nums.length;
        }
      
        int slow = 1; // 新数组的构建从索引 1 开始
        int repeat = 1; // 记录当前元素的重复次数

        for (int fast = 1; fast < nums.length; fast++) {
            // 遇到重复元素
            if (nums[fast] == nums[fast - 1]) {
                repeat++;
                // 如果重复次数小于等于 2,则保留该元素
                if (repeat <= 2) {
                    nums[slow++] = nums[fast];
                }
                // 如果 repeat > 2,则不移动 slow,fast 继续前进,相当于“跳过”了该元素
            } else { // 遇到新元素
                repeat = 1; // 重置计数器
                nums[slow++] = nums[fast]; // 保留新元素
            }
        }
        return slow;
    }
}

提交结果:

在这里插入图片描述

优缺点分析

  • 优点:逻辑非常清晰易懂,通过 repeat 计数器,我们能准确地知道每个元素的重复情况,易于理解和调试

四、 深度拓展:更通用的快慢指针模板

我们甚至可以摆脱 repeat 计数器,让逻辑更简洁,也更具扩展性,总结出一个此类题通用模板

核心思想

我们换一个角度思考:fast 指针指向的元素 nums[fast] 什么时候应该被保留?

答案是:当 nums[fast] 与新数组 [0...slow-1] 的倒数第二个元素 nums[slow-2] 不相等

为什么是 slow-2

  • nums[slow-1] 是新数组的最后一个元素
  • nums[slow-2] 是新数组的倒数第二个元素
  • 如果 nums[fast]nums[slow-2] 相等,那么 nums[slow-1] 必然也和它们相等,因为:数组有序(题目给的条件),即 nums[fast] 将是第三个重复的元素,应该被抛弃
  • 反之,如果 nums[fast]nums[slow-2] 不相等,那么 nums[fast] 最多只是第二个重复的元素,应该被保留

我们还需要处理一个边界情况:当 slow < 2 时,新数组的长度还不足 2,此时任何元素都应该被无条件保留

代码实现-通用模板

class Solution {
    public int removeDuplicates(int[] nums) {
        if (nums.length <= 2) {
            return nums.length;
        }
      
        int slow = 2; // 新数组的前两个元素 [0, 1] 默认保留
      
        for (int fast = 2; fast < nums.length; fast++) {
            // 检查 fast 指向的元素是否应该被保留
            // 如果 nums[fast] 不等于新数组的倒数第二个元素 nums[slow-2]
            // 说明 nums[fast] 不是第三个重复元素,可以保留
            if (nums[fast] != nums[slow - 2]) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }
}

提交结果:

在这里插入图片描述

优缺点分析

  • 优点
    • 代码更简洁,没有额外的状态变量
    • 扩展性极强:如果题目改成“最多保留 k 个重复项”,我们只需将 2 替换为 k 即可:if (slow < k || nums[fast] != nums[slow - k])。这是一个非常强大的通用模板
  • 缺点:逻辑相对抽象,需要深刻理解 slow-2 的含义,以及为什么可以这样:题目说是有序

五、 总结

解法一 (计数器辅助)解法二 (通用模板)
核心逻辑通过 repeat 计数器判断是否保留通过与 nums[slow-2] 比较来判断是否保留
状态变量slow, fast, repeatslow, fast
代码简洁性中等
扩展性需要修改 repeat <= 2 的判断只需将 2 替换为 k 即可
时间/空间复杂度O(N) / O(1)O(N) / O(1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值