第 1 章:数组与字符串(1) --- 快慢指针
数组基础
掌握数组的基本遍历、元素修改、插入、删除等操作,常与双指针或模拟一起出现。双指针包括快慢指针和对撞指针。
数组是一种顺序存储的数据结构,常见于各种算法题中,理解其操作与特性对于高效解题至关重要。以下我们将通过技巧讲解与经典例题,系统掌握数组的常规操作模式。
什么是快慢指针?
快慢指针(也叫双指针)是一种遍历数组/链表的常用技巧:
-
用两个下标指针
fast和slow来遍历或操作数组; -
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 练习题(按类型分类)
快慢指针类题目
-
- 删除有序数组中的重复项
-
- 删除有序数组中的重复项 II
-
- 移除元素
-
- 移动零
对撞指针类题目
-
- 两数之和 II(已排序数组)
-
- 验证回文串
-
- 反转字符串
模拟与分类技巧
-
- 颜色分类(荷兰国旗问题)
-
- 按奇偶排序数组
-
- 有序数组的平方
示例题解
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;
}

浙公网安备 33010602011771号