算法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 个

    • 给定值 比较 → 移除元素

相关推荐
NE_STOP32 分钟前
MyBatis-配置文件解读及MyBatis为何不用编写Mapper接口的实现类
java
后端AI实验室5 小时前
用AI写代码,我差点把漏洞发上线:血泪总结的10个教训
java·ai
CoovallyAIHub6 小时前
Moonshine:比 Whisper 快 100 倍的端侧语音识别神器,Star 6.6K!
深度学习·算法·计算机视觉
CoovallyAIHub7 小时前
速度暴涨10倍、成本暴降6倍!Mercury 2用扩散取代自回归,重新定义LLM推理速度
深度学习·算法·计算机视觉
CoovallyAIHub7 小时前
实时视觉AI智能体框架来了!Vision Agents 狂揽7K Star,延迟低至30ms,YOLO+Gemini实时联动!
算法·架构·github
CoovallyAIHub7 小时前
开源:YOLO最强对手?D-FINE目标检测与实例分割框架深度解析
人工智能·算法·github
程序员清风7 小时前
小红书二面:Spring Boot的单例模式是如何实现的?
java·后端·面试
belhomme7 小时前
(面试题)Redis实现 IP 维度滑动窗口限流实践
java·面试
CoovallyAIHub7 小时前
OpenClaw:从“19万星标”到“行业封杀”,这只“赛博龙虾”究竟触动了谁的神经?
算法·架构·github