算法D1-20260212:双指针专题

LeetCode 算法题解:双指针专题

目录

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

  2. 27. 移除元素 ⭐ 新增

  3. 28. 找出字符串中第一个匹配项的下标

  4. 80. 删除有序数组中的重复项 II ⭐ 新增


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

题目描述

给你一个 非严格递增排列 的数组 nums,请你 原地 删除重复出现的元素,使每个元素 只出现一次,返回删除后数组的新长度。

解法:双指针法

复制代码
class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        int j = 0;  // 慢指针
        
        for (int i = 0; i < n; i++) {  // i 是快指针
            if (nums[i] != nums[j]) {
                nums[++j] = nums[i];
            }
        }
        
        return j + 1;
    }
}

复杂度: 时间 O(n),空间 O(1)


27. 移除元素 ⭐

题目描述

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

关键区别: 与第 26 题不同,本题 不需要保持元素的相对顺序 ,且数组 无序

示例

示例 1:

复制代码
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:返回 k = 2,nums 前两个元素为 2

示例 2:

复制代码
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:返回 k = 5,前五个元素为 0,0,1,3,4(顺序可任意)

解法:双指针(快慢指针)

思路

使用快慢指针:

  • 快指针 :遍历数组,寻找不等于 val 的元素

  • 慢指针 idx:指向当前可以写入的位置

当快指针遇到不等于 val 的元素时,将其复制到慢指针位置,然后慢指针前进。

代码实现
复制代码
class Solution {
    public int removeElement(int[] nums, int val) {
        int idx = 0;  // 慢指针,指向下一个写入位置
        
        for (int num : nums) {  // 快指针遍历数组
            if (num != val) {
                nums[idx] = num;  // 保留不等于 val 的元素
                idx++;
            }
        }
        
        return idx;  // 返回新数组长度
    }
}
简化写法
复制代码
class Solution {
    public int removeElement(int[] nums, int val) {
        int idx = 0;
        for (int x : nums) {
            if (x != val) nums[idx++] = x;  // 先赋值,后自增
        }
        return idx;
    }
}
执行流程图解

nums = [0,1,2,2,3,0,4,2], val = 2 为例:

复制代码
初始: [0,1,2,2,3,0,4,2], idx=0

i=0, num=0 ≠ 2: nums[0]=0, idx=1  → [0,1,2,2,3,0,4,2]
i=1, num=1 ≠ 2: nums[1]=1, idx=2  → [0,1,2,2,3,0,4,2]
i=2, num=2 = 2: 跳过
i=3, num=2 = 2: 跳过
i=4, num=3 ≠ 2: nums[2]=3, idx=3  → [0,1,3,2,3,0,4,2]
i=5, num=0 ≠ 2: nums[3]=0, idx=4  → [0,1,3,0,3,0,4,2]
i=6, num=4 ≠ 2: nums[4]=4, idx=5  → [0,1,3,0,4,0,4,2]
i=7, num=2 = 2: 跳过

结果: 返回 5, nums = [0,1,3,0,4,_,_,_]
复杂度分析
指标 复杂度 说明
时间复杂度 O(n) 单次遍历数组
空间复杂度 O(1) 原地修改,常数额外空间
与 26 题的对比
特性 26. 删除重复项 27. 移除元素
数组状态 有序 无序
保留条件 与前一个元素不同 不等于给定值
顺序要求 保持相对顺序 不强制要求顺序
指针移动 比较 nums[i]nums[j] 直接与 val 比较

28. 找出字符串中第一个匹配项的下标

题目描述

在字符串 haystack 中找出 needle 的第一个匹配下标。

解法:暴力匹配

复制代码
class Solution {
    public int strStr(String haystack, String needle) {
        int hs = haystack.length();
        int nd = needle.length();
        char[] hsArray = haystack.toCharArray();
        char[] ndArray = needle.toCharArray();
        
        for (int i = 0; i <= hs - nd; i++) {
            int p = i, k = 0;
            while (k < nd && hsArray[p] == ndArray[k]) {
                p++;
                k++;
            }
            if (k == nd) return i;
        }
        return -1;
    }
}

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

题目描述

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

核心变化: 从「保留 1 次」变为「保留最多 2 次」

示例

示例 1:

复制代码
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:元素 1 出现 3 次,只保留前 2 个

示例 2:

复制代码
输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:元素 1 出现 4 次,只保留前 2 个

解法一:双指针(直接法)

思路

与第 26 题类似,但需要保留最多 2 个重复元素:

  • 慢指针 slow:从索引 2 开始,指向下一个写入位置

  • 快指针 fast:从索引 2 开始,遍历数组

  • 保留条件nums[fast]nums[slow-2] 不同(确保当前元素出现次数不超过 2 次)

代码实现
复制代码
class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        
        // 数组长度小于等于 2 时,直接返回长度
        if (n <= 2) {
            return n;
        }
        
        int slow = 2;  // 慢指针从 2 开始(前两个元素直接保留)
        int fast = 2;  // 快指针从 2 开始遍历
        
        while (fast < n) {
            // 关键:与 slow-2 位置比较,确保最多保留 2 个相同元素
            if (nums[slow - 2] != nums[fast]) {
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        
        return slow;
    }
}
执行流程图解

nums = [1,1,1,2,2,3] 为例:

复制代码
初始: [1,1,1,2,2,3], slow=2, fast=2

fast=2, nums[0]=1 == nums[2]=1: 跳过(已有 2 个 1)
fast=3, nums[0]=1 != nums[3]=2: nums[2]=2, slow=3 → [1,1,2,2,2,3]
fast=4, nums[1]=1 != nums[4]=2: nums[3]=2, slow=4 → [1,1,2,2,2,3]
fast=5, nums[2]=2 != nums[5]=3: nums[4]=3, slow=5 → [1,1,2,2,3,3]

结果: 返回 5, nums = [1,1,2,2,3,_]

解法二:通用解法(推荐)

利用之前提到的通用模板,将 k 设为 2:

复制代码
class Solution {
    public int removeDuplicates(int[] nums) {
        return process(nums, 2);  // 最多保留 2 个
    }
    
    /**
     * 通用解法:保留最多 k 个相同元素
     */
    int process(int[] nums, int k) {
        int idx = 0;
        for (int x : nums) {
            // 前 k 个直接保留,或者与前面第 k 个元素不同时保留
            if (idx < k || nums[idx - k] != x) {
                nums[idx++] = x;
            }
        }
        return idx;
    }
}

解法三:官方变形通用写法

复制代码
public static int process(int[] nums, int exist) {
    int n = nums.length;
    if (n <= exist) return n;  // 小于等于 exist 直接返回
    
    int slow = exist;  // 慢指针从 exist 开始
    int fast = exist;  // 快指针从 exist 开始
    
    while (fast < n) {
        // 与前面第 exist 个元素比较
        if (nums[slow - exist] != nums[fast]) {
            nums[slow] = nums[fast];
            slow++;
        }
        fast++;
    }
    
    return slow;
}

exist = 2 时,就是本题的解法。

三题对比总结

题目 保留次数 关键判断条件 慢指针起始位置
26. 删除重复项 1 次 nums[i] != nums[j] 0
27. 移除元素 保留非 val num != val 0
80. 删除重复项 II 2 次 nums[slow-2] != nums[fast] 2

通用模板总结

复制代码
/**
 * 通用模板:在有序数组中保留最多 k 个相同元素
 * 适用于:26题(k=1), 80题(k=2) 等
 */
int removeDuplicatesGeneral(int[] nums, int k) {
    int idx = 0;
    for (int x : nums) {
        if (idx < k || nums[idx - k] != x) {
            nums[idx++] = x;
        }
    }
    return idx;
}

完整代码汇总

复制代码
// 26. 删除有序数组中的重复项(保留1个)
class Solution26 {
    public int removeDuplicates(int[] nums) {
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != nums[j]) {
                nums[++j] = nums[i];
            }
        }
        return j + 1;
    }
}

// 27. 移除元素
class Solution27 {
    public int removeElement(int[] nums, int val) {
        int idx = 0;
        for (int x : nums) {
            if (x != val) nums[idx++] = x;
        }
        return idx;
    }
}

// 28. 找出字符串中第一个匹配项的下标
class Solution28 {
    public int strStr(String haystack, String needle) {
        int hs = haystack.length(), nd = needle.length();
        char[] h = haystack.toCharArray(), n = needle.toCharArray();
        
        for (int i = 0; i <= hs - nd; i++) {
            int p = i, k = 0;
            while (k < nd && h[p] == n[k]) {
                p++; k++;
            }
            if (k == nd) return i;
        }
        return -1;
    }
}

// 80. 删除有序数组中的重复项 II(保留2个)
class Solution80 {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if (n <= 2) return n;
        
        int slow = 2, fast = 2;
        while (fast < n) {
            if (nums[slow - 2] != nums[fast]) {
                nums[slow++] = nums[fast];
            }
            fast++;
        }
        return slow;
    }
}

学习要点

  1. 双指针技巧 是解决数组原地修改问题的核心

  2. 通用模板 process(nums, k) 可以应对「保留 k 个重复元素」的所有情况

  3. 边界处理 是小数组(长度 ≤ k)时需要特殊处理

  4. 比较对象 决定了解法的差异:

    • 前一个元素 比较 → 保留 1 个

    • 前 k 个元素 比较 → 保留 k 个

    • 给定值 比较 → 移除元素

相关推荐
仟濹2 小时前
【算法打卡day7(2026-02-12 周四)算法:BFS and BFS】10__卡码网110_字符串迁移, 11_卡码网105_有向图的完全连通
算法·深度优先·dfs·bfs·宽度优先
935962 小时前
机考24 翻译18 单词11
c语言·数据结构·算法
爱思德学术2 小时前
中国计算机学会(CCF)推荐学术会议-B(计算机体系结构/并行与分布计算/存储系统):SPAA 2026
算法
Codiggerworld2 小时前
从字节码到JVM:深入理解Java的“一次编写,到处运行”魔法
java·开发语言·jvm
2 小时前
2.12矩阵问题,发牌,数字金字塔
线性代数·算法·矩阵
_codemonster2 小时前
配置Tomcat时为啥要配置Artifacts
java·tomcat·firefox
无心水2 小时前
2025,一路有你!
java·人工智能·分布式·后端·深度学习·架构·2025博客之星
无聊的小坏坏2 小时前
一文讲通:二分查找的边界处理
数据结构·c++·算法
m0_528749002 小时前
C语言错误处理宏两个比较重要的
java·linux·算法