1. 算法简介
双指针(Two Pointers)是一种非常优雅、直观且在面试中极其高频的算法思想。它并不是一种具体的数据结构,而是一种优化遍历策略的技巧。
核心思想:使用两个变量(通常叫 left 和 right,或者 fast 和 slow)作为索引,在数组或链表上同时进行遍历。通过两个指针的相互配合,通常可以将原本 的时间复杂度降为 ,且空间复杂度保持在 。
双指针主要分为两大流派:相向双指针 和 同向双指针(快慢指针)。
2. 相向双指针 (一左一右,向中间靠拢)
2.1 算法介绍
两个指针分别放置在数组(或序列)的两端。left 指针在最左侧起步,right 指针在最右侧起步。根据特定的条件,两个指针逐步向中间逼近,直到相遇或错开。
2.2 适用场景
条件反射:看到**“已排序”、“升序”且需要查找组合**(如两数之和),或者需要进行首尾对称操作(如反转字符串、判断回文串)时,优先考虑相向双指针。
2.3 经典示例 1:有序数组的两数之和
题目:在已升序排列的整数数组中找出两个数,使它们的和等于目标值 target。
Python 实现:
def twoSum(numbers: list[int], target: int) -> list[int]:
left, right = 0, len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left, right]
elif current_sum > target:
right -= 1 # 和太大,右指针左移找更小的
else:
left += 1 # 和太小,左指针右移找更大的
return [-1, -1]
Java 实现:
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) return new int[]{left, right};
else if (sum > target) right--;
else left++;
}
return new int[]{-1, -1};
}
}
2.4 经典示例 2:反转字符串
题目:原地反转字符串数组( 额外空间)。
Python 实现:
def reverseString(s: list[str]) -> None:
left, right = 0, len(s) - 1
while left < right:
# Python 优雅的元组解包交换
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
Java 实现:
class Solution {
public void reverseString(char[] s) {
int left = 0, right = s.length - 1;
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
3. 同向双指针 / 快慢指针 (同侧出发,一前一后)
3.1 算法介绍
两个指针都站在起跑线(最左侧),朝着同一个方向前进,只是移动的速度或触发条件不同。
fast(快指针 / 探路先锋):在前面探路,通常每一步都会往前走,检查每一个元素。slow(慢指针 / 大本营):走得稳。只有当“探路先锋”找到了符合条件的新元素时,慢指针才会操作并往前走。
3.2 适用场景
通常用于 数组原地覆盖/去重、元素的移除/归位(如移动零),以及 链表找中点/判断环。
3.3 经典示例 1:删除有序数组中的重复项
题目:原地删除重复出现的元素,使每个元素只出现一次,返回新长度。
Python 实现:
def removeDuplicates(nums: list[int]) -> int:
if not nums: return 0
slow = 0
# 快指针从第二个元素开始探路
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]: # 发现不重复的新元素
slow += 1
nums[slow] = nums[fast] # 接收新元素
return slow + 1
Java 实现:
class Solution {
public int removeDuplicates(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int slow = 0;
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
}
3.4 经典示例 2:移动零
题目:将数组中所有 0 移动到末尾,保持非 0 元素的相对顺序。
Python 实现:
def moveZeroes(nums: list[int]) -> None:
slow = 0
# 第一步:把非 0 元素全部按顺序挪到前面
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
# 第二步:剩下的位置全部填 0
while slow < len(nums):
nums[slow] = 0
slow += 1
Java 实现:
class Solution {
public void moveZeroes(int[] nums) {
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != 0) {
nums[slow] = nums[fast];
slow++;
}
}
while (slow < nums.length) {
nums[slow] = 0;
slow++;
}
}
}
4. 复杂度总结
以上所有双指针算法的时间和空间复杂度均为:
- 时间复杂度:,指针仅对数据进行单次/恒定次数的线性扫描,无嵌套循环。
- 空间复杂度:,仅使用了常数级别的额外指针变量,全部为原地操作。