第 1 章:数组与字符串(1) --- 快慢指针

数组基础

掌握数组的基本遍历、元素修改、插入、删除等操作,常与双指针或模拟一起出现。双指针包括快慢指针和对撞指针。

数组是一种顺序存储的数据结构,常见于各种算法题中,理解其操作与特性对于高效解题至关重要。以下我们将通过技巧讲解与经典例题,系统掌握数组的常规操作模式。

什么是快慢指针?

快慢指针(也叫双指针)是一种遍历数组/链表的常用技巧:

  • 用两个下标指针 fastslow 来遍历或操作数组;

  • fast 用来遍历整个数组(即“读”数据);

  • slow 用来构建结果(即“写”数据)或控制逻辑。

这种方法特别适合处理原地修改数组的问题,比如去重、删除元素、移动元素等。其核心思想是利用两个速度不同的指针,把有效元素筛选出来,集中移动到数组左边,从而达到省空间、提效率的目的。

// 快慢指针基本模板(数组原地修改类)
int slow = 0;
int fast = 0;
int n = nums.size();
for(; fast < n; fast++)
{
    if(满足某种条件)
    {
        nums[slow] = nums[fast];
        slow++;
    }
}

什么是对撞指针?

对撞指针常用于已排序数组/区间的场景,左右两个指针分别从数组两端向中间靠拢。

它常用于查找、验证、双向遍历等问题,避免重复计算,提高效率。

常用于解决以下问题:

  • 判断是否存在一对数的和等于目标值(Two Sum)

  • 判断是否为回文数组或字符串

  • 双指针夹逼优化枚举(如三数之和)

int left = 0, right = nums.size() - 1;
while (left < right) 
{
    int sum = nums[left] + nums[right];
    if (sum == target) 
    {
        return true;
    } 
    else if (sum < target) 
    {
        left++;
    } 
    else 
    {
        right--;
    }
}

数组中的插入与删除(模拟)

虽然在 C++ 中 vector 提供了 insert 和 erase 等接口,但频繁使用这些操作会带来 O(n) 的性能开销。

在实际 coding 中,为了追求原地修改、节省空间,我们常常用快慢指针来模拟“插入”或“删除”的过程。

比如删除某些特定元素、保留满足条件的元素、将零元素移动到末尾等问题,都可以用这种技巧解决。

例题:LeetCode 27 移除元素、203 删除链表元素(类似思路)

技巧:

  • 维护一个 write 指针用于写入有效值

  • 可以使用 while 或 for 循环控制快慢指针

这种方式的优势在于:无需额外开辟数组,能够在原数组上进行操作,节省空间并提高代码执行效率。

LeetCode 练习题(按类型分类)

快慢指针类题目

    1. 删除有序数组中的重复项
    1. 删除有序数组中的重复项 II
    1. 移除元素
    1. 移动零

对撞指针类题目

    1. 两数之和 II(已排序数组)
    1. 验证回文串
    1. 反转字符串

模拟与分类技巧

    1. 颜色分类(荷兰国旗问题)
    1. 按奇偶排序数组
    1. 有序数组的平方

示例题解

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

题目类型:快慢指针 / 原地删除
思路简述

  • 数组有序,重复元素在一起;

  • slow 表示写入位置,fast 表示读入位置;

  • nums[fast] != nums[slow-1] 时,更新 slow 位置;

这个问题的重点是去重但保留顺序,因此使用快慢指针记录当前合法元素的写入位置。

int removeDuplicates(vector<int>& nums) 
{
    if (nums.empty()) return 0;
    int slow = 1;
    for (int fast = 1; fast < nums.size(); fast++)
    {
        if (nums[fast] != nums[slow - 1])
        {
            nums[slow++] = nums[fast];
        }
    }
    return slow;
}

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

题目类型:快慢指针
思路简述

  • 每个元素最多允许出现 2 次

  • 快指针遍历数组,慢指针记录有效写入位置

  • nums[fast] != nums[slow - 2] 时,将其写入 slow 位置

这个题目是“去重”的变形,需要灵活使用条件限制保留数量。

int removeDuplicates(vector<int>& nums)
{
    int n = nums.size();
    if (n <= 2) return n;
    int slow = 2;
    for (int fast = 2; fast < n; fast++) 
    {
        if (nums[fast] != nums[slow - 2])
        {
            nums[slow++] = nums[fast];
        }
    }
    return slow;
}

27. 移除元素

题目类型:快慢指针 / 原地删除

删除目标元素并返回数组新长度,保持其他元素的相对顺序不变。

int removeElement(vector<int>& nums, int val) 
{
    int slow = 0;
    for (int fast = 0; fast < nums.size(); fast++)
    {
        if (nums[fast] != val) 
        {
            nums[slow++] = nums[fast];
        }
    }
    return slow;
}

283. 移动零

题目类型:快慢指针

经典问题:将所有 0 移动到末尾,保持非零元素顺序不变。

void moveZeroes(vector<int>& nums) 
{
    int slow = 0;
    for (int fast = 0; fast < nums.size(); fast++) 
    {
        if (nums[fast] != 0)
        {
            swap(nums[slow++], nums[fast]);
        }
    }
}

75. 颜色分类(荷兰国旗问题)

题目类型:三指针 / 分类排序

三类元素(0、1、2)原地排序。需要维护三个指针:low 指向 0 的下一个位置,high 指向 2 的下一个位置,mid 为当前判断位置。

void sortColors(vector<int>& nums)
{
    int low = 0, mid = 0, high = nums.size() - 1;
    while (mid <= high)
    {
        if (nums[mid] == 0)
        {
            swap(nums[low++], nums[mid++]);
        } 
        else if (nums[mid] == 1) 
        {
            mid++;
        } 
        else 
        {
            swap(nums[mid], nums[high--]);
        }
    }
}

905. 按奇偶排序数组

题目类型:双指针

将所有偶数排在前面,奇数排在后面。适合使用对撞指针实现。

vector<int> sortArrayByParity(vector<int>& nums)
{
    int left = 0, right = nums.size() - 1;
    while (left < right)
    {
        if (nums[left] % 2 > nums[right] % 2)
        {
            swap(nums[left], nums[right]);
        }
        if (nums[left] % 2 == 0) left++;
        if (nums[right] % 2 == 1) right--;
    }
    return nums;
}
posted @ 2025-04-24 20:47  DevByHe  阅读(26)  评论(0)    收藏  举报