双指针(Two Pointers)是解决数组/链表问题最优雅的技巧之一。通过两个指针的协同移动,将 O(n²) 暴力枚举优化为 O(n) 线性扫描。
加载中…
应用场景:有序数组 · 原地操作 · 回文判断
双指针(Two Pointers)是解决数组/链表问题最优雅的技巧之一。通过两个指针的协同移动,将 O(n²) 暴力枚举优化为 O(n) 线性扫描。
交互式动画演示,可调整参数并单步执行。
全屏打开graph LR A[双指针] --> B[对撞: 左右相向] A --> C[快慢: 间隔移动] A --> D[滑动窗口: 同向伸缩]
两个指针从两端向中间靠拢,常用于有序数组。
// 两数之和 II(有序数组)LeetCode 167
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 + 1, right + 1};
else if (sum < target) left++;
else right--;
}
return new int[]{-1, -1};
}两个指针同向移动,速度不同,常用于链表环检测。
// 环形链表 II LeetCode 142
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
ListNode ptr = head;
while (ptr != slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}用于原地删除、去重、分区等操作。
// 移除元素 LeetCode 27
public int removeElement(int[] nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
return slow;
}| 题目 | 模式 | 关键思路 |
|---|---|---|
| 两数之和 II | 对撞 | 有序 → 和小左移,和大右移 |
| 三数之和 | 对撞 | 固定一个数 + 对撞 |
| 盛水最多的容器 | 对撞 | 移动较短的一侧 |
| 环形链表 | 快慢 | 快2慢1,相遇则有环 |
| 链表中点 | 快慢 | 快到尾,慢到中 |
| 移除元素 | 分离 | 快指针探索,慢指针写入 |
| 删除有序数组重复项 | 分离 | 不等则写入 |
| 颜色分类(荷兰国旗) | 三指针 | lt/gt/i 三路分区 |
// 颜色分类 LeetCode 75
public void sortColors(int[] nums) {
int lt = 0;
int gt = nums.length;
int i = 0;
while (i < gt) {
if (nums[i] == 0) {
swap(nums, i, lt);
lt++; i++;
} else if (nums[i] == 2) {
gt--;
swap(nums, i, gt);
} else {
i++;
}
}
}对撞指针的核心不变量:被跳过的组合一定不是答案。
以"盛水最多的容器"为例:
以“有序数组两数之和”(target=9, nums=[2,7,11,15])为例:
初始: l=0, r=3
[2, 7, 11, 15] sum = 2+15 = 17 > 9 → r--
l r
[2, 7, 11, 15] sum = 2+11 = 13 > 9 → r--
l r
[2, 7, 11, 15] sum = 2+7 = 9 == 9 → 找到![0,1]
l r
为什么不需要回头?
对撞指针不变量:sum > target → r--(右端太大);sum < target → l++(左端太小)。
当 l=0,r=3 时只考察了 (0,3),sum=17>target 所以 r--;此后 (0,2) 只会更小。
| 题目 | 难度 | 模式 | 核心思路 |
|---|---|---|---|
| 两数之和 II(LC 167) | 🟡 Medium | 对撞 | 有序数组,sum 大了 r--,小了 l++ |
| 盛水最多的容器(LC 11) | 🟡 Medium | 对撞 | 移动较矮的一侧 |
| 三数之和(LC 15) | 🟡 Medium | 对撞 | 排序 + 固定一个数 + 对撞 + 去重 |
| 删除有序数组重复项(LC 26) | 🟢 Easy | 分离 | 快指针探索,慢指针写入 |
| 移动零(LC 283) | 🟢 Easy | 分离 | 非零元素前移 |
| 环形链表(LC 141) | 🟢 Easy | 快慢 | 快2慢1,相遇即有环 |
| 链表中点(LC 876) | 🟢 Easy | 快慢 | 快指针到尾,慢指针到中 |
| 接雨水(LC 42) | 🔴 Hard | 对撞 | 左右最大值中较小的决定水量 |
1. 循环条件写错
// 对撞指针:✅ while (l < r),❌ while (l <= r)(三数之和中会重复计算)
// 快慢指针:✅ while (fast != null && fast.next != null)
2. 三数之和忘记去重
// ❌ 只去重内层,外层 nums[i] == nums[i-1] 时也要跳过
if (i > 0 && nums[i] == nums[i - 1]) continue;
// 内层找到解后:
while (l < r && nums[l] == nums[l + 1]) l++;
while (l < r && nums[r] == nums[r - 1]) r--;
3. 快慢指针找中点的奇偶差异
[1,2,3,4]: fast=head 时 slow 停在 3(偏右中点)
fast=head.next 时 slow 停在 2(偏左中点)
链表归并排序必须用偏左中点,否则两个节点时死循环
练习推荐:按 LC 167 → LC 11 → LC 15 的顺序练习对撞指针,再刷快慢指针专题。