Lee's Blog
  • 首页
  • 博客
  • 作品
  • 成长
  • 学习
  • 打榜
  • 练习
  • 关于
登录 / 注册
✦

保持联系

关注我的最新动态

GitHubbilibili

© 2026 Lee's Blog

首页/博客/算法学习/算法踩坑-同向快慢指针
算法踩坑-同向快慢指针
算法学习2026年7月20日7 分钟阅读

算法踩坑-同向快慢指针

算法学习
X微博

同向快慢指针深度拆解:LeetCode26 有序去重 VS LeetCode27 移除指定元素

博客地址模板适配 https://blog.dogeggcode.cyou/blog/algo1,风格:实战踩坑导向、代码对比、VSCode调试验证、避坑清单

前言

原地修改数组是算法入门高频考点,同向快慢指针是最优解(时间O(N)、空间O(1))。很多初学者刷完26题,照搬代码写27直接翻车,我自己调试时踩满一堆逻辑坑,本文结合两道同源题型,拆解指针起点、判断逻辑、返回值三大核心差异,统一快慢指针通用模板,附带VSCode调试排错方法。

一、两道原题题干速览

LeetCode 26 删除有序数组中的重复项

条件:数组升序有序,重复元素相邻;要求原地去重,每个元素只保留1次,返回有效长度。 示例:nums=[1,1,2] → 返回长度2,数组前两位[1,2]

LeetCode 27 移除元素

条件:数组无序/有序均可,任意位置都可能出现目标值val;要求原地删除所有等于val的数字,返回有效长度。 示例:nums=[0,1,2,2,3,0,4,2], val=2 → 返回长度5,数组前五位[0,1,3,0,4]

二、LeetCode26 标准正确代码 & 逻辑解析

完整Java实现(我写的可AC版本)

class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if ( n == 0 ) {
            return 0;
        }
        // 快慢指针统一从下标1开始
        int fast = 1 , slow = 1;
        while ( fast < n ) {
            // 判断:快指针和前一位有效元素不重复,才存入慢指针
            if ( nums[fast-1] != nums[fast] ) {
                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }
        // slow直接等于有效元素总个数,无需+1
        return slow;
    }
}

核心逻辑拆解

  1. 指针起点 slow=1、fast=1 数组有序,第一个元素nums[0]一定保留,天然是合法有效数字。slow代表「下一个可写入有效数字的空位」,初始跳过已占用的0号位。
  2. 判断条件 nums[fast-1] != nums[fast] 利用有序特性:重复数字一定挨在一起,fast-1是上一个存入slow区间的最后一个数字,两者不同说明是全新不重复元素,需要保存。
  3. 返回值 slow slow记录一共写入了多少个有效数字,数值直接等于数组有效长度。

推演示例 nums=[1,1,2]

初始 slow=1,fast=1

  1. fast=1:nums[0]==nums[1] 重复,仅fast++ → fast=2
  2. fast=2:nums[1]!=nums[2],nums[1]=2,slow=2,fast++ → fast=3 循环结束,return slow=2,结果正确。

三、LeetCode27 标准正确代码 & 逻辑解析

完整可AC Java代码(我多次改错后的最终版本)

class Solution {
    public int removeElement(int[] nums, int val) {
        int slow = 0 ;
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        // fast从头完整遍历所有元素
        for (int fast = 0 ; fast < n ; fast ++){
            // 判断:当前fast元素不是要删除的值,才存入slow
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow ++;
            }
        }
        // 直接返回slow,禁止画蛇添足+1
        return slow;
    }
    // 本地VSCode调试入口
    public static void main(String[] args) {
        int[] nums = {0,1,2,2,3,0,4,2};
        int k = new Solution().removeElement(nums,2);
        System.out.println("有效长度k="+k);
        System.out.println("前k个元素:"+Arrays.toString(Arrays.copyOf(nums,k)));
    }
}

核心逻辑拆解

  1. 指针起点 slow=0、fast=0 数组无有序保障,第一个元素有可能等于val,需要直接丢弃,不能默认保留,因此快慢指针全部从0开始遍历。
  2. 判断条件 nums[fast] != val 无相邻对比逻辑,只筛选:当前遍历元素不等于待删除值,才复制到slow的有效区间。
  3. 返回值 slow slow每存入一个合法数字自增1,最终数值就是有效元素总数。

样例推演 nums=[0,1,2,2,3,0,4,2], val=2

循环结束后slow=5,前5位[0,1,3,0,4],和题目预期完全匹配。

四、两道题核心差异对比(踩坑根源)

对比维度 LeetCode26 有序去重 LeetCode27 移除指定值 你踩过的坑
指针起始下标 slow=1 fast=1 slow=0 fast=0 照搬26的1作为起点,第一个val会被错误保留
判断逻辑 对比fast-1和fast相邻元素是否重复 直接判断nums[fast]是否等于val 用fast-1对比,连续val覆盖后依旧残留目标值
前置前提 数组升序有序,首元素必保留 无序,任意元素都可能删除 忽略有序前提,把相邻对比逻辑套用到无序数组
slow含义 下一个写入空位,跳过已占用0号位 从0开始,从头收集所有合法数字 计数逻辑混乱,返回时错误写slow+1
返回值 return slow return slow 两道题都额外+1,有效长度全部算错

最致命误区复盘

之前修改多版代码全部WA,本质是把26有序数组的特殊逻辑,直接套用到无序过滤题型:

  1. 错误用fast=1跳过首元素,首元素为val时无法删除;
  2. 错误判断nums[fast-1]==val就覆盖,连续val场景只会原地替换,无法清除;
  3. 循环结束无脑return slow+1,有效长度多算1位。

五、通用同向快慢指针万能模板(覆盖26、27、283移动零)

模板定义

  • slow:写指针,维护合法元素区间,代表下一个合法数字的存储位置
  • fast:读指针,完整遍历数组每一个元素,筛选符合条件的值

通用框架

public int template(int[] nums, int filterVal){
    int slow = 0;
    for(int fast = 0; fast < nums.length; fast++){
        // 条件:保留当前fast元素的规则,根据题目修改
        if( 符合保留规则 ){
            nums[slow++] = nums[fast];
        }
    }
    // slow一定等于有效长度,永远不要+1
    return slow;
}

模板适配两道题

  1. LeetCode27适配:保留规则 nums[fast] != val
  2. LeetCode26适配:需要有序前置判断,特殊修改起点与对比逻辑(属于模板特例)

六、VSCode调试排错实操(针对这道题)

1. 调试配置

安装扩展Extension Pack for Java,代码内写入main测试用例,右键main选择Debug Java启动。

2. 关键断点设置

  1. slow/fast初始化行:观察初始指针数值;
  2. if判断条件行:每次进入分支查看数组变化;
  3. return slow行:循环结束查看slow最终值。

3. 变量面板观察要点

  • Local区域展开nums数组,实时查看原地修改后的数值;
  • 监视窗口添加slow、fast、nums[fast],跟踪指针变化;

4. 快速定位BUG技巧

  1. 输出前k位包含待删除值 → 判断条件写反/指针起点错误;
  2. 返回长度比预期大1 → return多写+1;
  3. 连续目标值无法清除 → 用了fast-1相邻对比逻辑。

七、避坑总结(刷题必看)

  1. 有序数组去重(26)是特例:利用相邻重复特性,指针从1开始;无序过滤类题目(27、283)全部统一指针从0起步;
  2. slow自增时机:只有存入合法数字时才++,不要在匹配删除条件时计数;
  3. 返回值铁律:同向快慢指针通用模板中,slow直接等于有效长度,禁止额外+1;
  4. 不要盲目照搬AC代码:看清题目是否有序、首元素是否一定保留,再复用逻辑。

拓展习题

  1. LeetCode283 移动零(完全适配通用模板)
  2. LeetCode83 删除排序链表中的重复项(链表版26逻辑)
  3. LeetCode80 删除有序数组中的重复项II(进阶限制最多保留2个重复元素)

博客底部标签

#算法 #双指针 #LeetCode #Java #数组原地修改 #VSCode调试 需要我把这篇博客精简成掘金Markdown紧凑版,或者补充你调试时完整的逐轮变量推演表格吗?

← 上一篇算法踩坑-二分法下一篇 →算法踩坑-相向左右指针 | 刷题踩坑连载 03

相关文章

  • 算法踩坑-相向左右指针 | 刷题踩坑连载 03约 7 分钟
  • 算法踩坑-二分法约 6 分钟

评论

需要登录账号才能发表评论。

  • 加载中…

本页内容

  • 同向快慢指针深度拆解:LeetCode26 有序去重 VS LeetCode27 移除指定元素
  • 前言
  • 一、两道原题题干速览
  • LeetCode 26 删除有序数组中的重复项
  • LeetCode 27 移除元素
  • 二、LeetCode26 标准正确代码 & 逻辑解析
  • 完整Java实现(我写的可AC版本)
  • 核心逻辑拆解
  • 推演示例 `nums=[1,1,2]`
  • 三、LeetCode27 标准正确代码 & 逻辑解析
  • 完整可AC Java代码(我多次改错后的最终版本)
  • 核心逻辑拆解
  • 样例推演 `nums=[0,1,2,2,3,0,4,2], val=2`
  • 四、两道题核心差异对比(踩坑根源)
  • 最致命误区复盘
  • 五、通用同向快慢指针万能模板(覆盖26、27、283移动零)
  • 模板定义
  • 通用框架
  • 六、VSCode调试排错实操(针对这道题)
  • 1. 调试配置
  • 2. 关键断点设置
  • 3. 变量面板观察要点
  • 4. 快速定位BUG技巧
  • 七、避坑总结(刷题必看)
  • 拓展习题
  • 博客底部标签