第2类题型:双指针
为什么双指针题看起来不难,你一到面试就容易写乱
很多同学第一次刷双指针时,会觉得这类题比哈希表还“直观”。因为代码通常不长,变量也常常只有
left、right、slow、fast四个名字。但真正到了面试现场,双指针反而很容易暴露出两类问题:
- 你会套模板,但说不清两个指针各自代表什么。
- 你知道要移动某一边,却解释不出“为什么这样移动不会漏解”。
- 你能把
三数之和写个大概,却总在去重和边界上翻车。- 你把“会写代码”当成“理解题型”,结果一换题面就不稳。
如果你现在正处在“Easy 基本能做,Medium 一追问就虚”的阶段,双指针是非常值得单独攻克的一类题。读完这篇,你至少要把 3 件事练熟:先判断是快慢指针还是左右指针、能把代表题写稳、能在面试里解释每一步为什么这么移动。
文章目录
- 第2类题型:双指针
-
- 1. 核心知识点
-
- 动画辅助理解:固定一个数,再让左右指针夹逼
- 2. 这类题在面试里考什么
- 3. 高频题清单
- 4. 这类题最容易犯的 3 个错误
- 5. 代表题精讲 1
-
- 题目
- 思路
- Java 代码
- 手推一遍最容易记住的状态变化
- 复杂度
- 如果这是面试现场,你可以这样说
- 题目
- 6. 代表题精讲 2
-
- 题目
- 思路
- Java 代码
- 手推一遍最能看懂去重和移动逻辑
- 复杂度
- 如果这是面试现场,你可以这样说
- 题目
- 7. 其余题模板与关键片段
-
- [`26. 删除有序数组中的重复项`](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/)
- [`11. 盛最多水的容器`](https://leetcode.cn/problems/container-with-most-water/)
- [`26. 删除有序数组中的重复项`](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/)
- 8. 边界、易混点与替代方案
-
- 快慢指针最容易错在哪
- 左右指针最容易错在哪
- `三数之和` 为什么总有人写错
- 这类题怎么判断值不值得用双指针
- 快慢指针最容易错在哪
- 9. 你学完后怎么验证自己真的会了
- 10. 错题本记录方式
- 11. 适用范围与边界
- 12. 面试前 3 分钟速记
- 13. 结尾:把“会套模板”变成“会判断、会证明、会自测”
1. 核心知识点
双指针本质上有两类模型:
- 快慢指针:常用于原地覆盖、去重、移动元素。
- 左右指针:常用于有序数组、逼近目标值、利用单调性缩小范围。
识别信号很常见:
- 数组要原地修改。
- 已排序,或者可以先排序。
- 要找两边夹逼、最优面积、和问题。
- 存在去重细节。
最基础的快慢指针模板是:
1int slow = 0; 2for (int fast = 0; fast < nums.length; fast++) { 3 if (满足保留条件) { 4 nums[slow] = nums[fast]; 5 slow++; 6 } 7} 8
左右指针模板则更像:
1int left = 0; 2int right = nums.length - 1; 3while (left < right) { 4 // 根据条件移动 left 或 right 5} 6
真正决定你能不能写对的,不是模板本身,而是你能不能说清楚两个指针各自代表什么,以及每一步移动为什么不会漏答案。
动画辅助理解:固定一个数,再让左右指针夹逼
双指针最适合用 15. 三数之和 建立第一印象。你可以先打开本地动画页:
这个动画最值得观察的是两件事:
- 外层先固定一个数
nums[i]。 - 内层用
left和right在剩余区间里根据和的大小夹逼。

2. 这类题在面试里考什么
双指针题看起来不难,但特别能暴露“会背模板”和“真正理解不变量”的差距。
面试官通常在看:
- 你是否能解释两个指针的含义。
- 你为什么移动左边而不是右边。
- 你如何证明不会漏掉最优解。
- 你是否处理好了去重和边界。
很多候选人会写出一个大概对的结构,但一旦被追问“为什么这一步能这么移”,就说不清楚。这正是双指针题的区分度。
3. 高频题清单
| 题目 | 来源 | 难度 | 高频属性 |
|---|---|---|---|
| 283. 移动零 | LeetCode 热题 100 | Easy | 面试高频 |
| 26. 删除有序数组中的重复项 | 面试经典 150 | Easy | 基础高频 |
| 11. 盛最多水的容器 | LeetCode 热题 100 | Medium | 高频 Medium |
| 15. 三数之和 | LeetCode 热题 100 | Medium | 真实面经高频 |
4. 这类题最容易犯的 3 个错误
- 双指针能做的题,还在用多重循环硬枚举。
- 左右指针会移动,但说不清为什么这么移动不会漏解。
三数之和这类题排序之后,去重细节没有写全。
5. 代表题精讲 1
题目
思路
这题是最典型的快慢指针。要求把所有 0 移到数组末尾,同时保持非零元素的相对顺序。
可以把 slow 理解为“下一个非零元素应该放的位置”,把 fast 理解为“当前正在扫描的位置”。
做法:
- 用
fast从左到右扫描。 - 如果
nums[fast] != 0,就把它放到nums[slow],然后slow++。 - 第一轮结束后,
[0, slow - 1]已经是所有非零元素。 - 再把
[slow, n - 1]全部填成0。
Java 代码
1class Solution { 2 public void moveZeroes(int[] nums) { 3 int slow = 0; 4 for (int fast = 0; fast < nums.length; fast++) { 5 if (nums[fast] != 0) { 6 nums[slow] = nums[fast]; 7 slow++; 8 } 9 } 10 11 while (slow < nums.length) { 12 nums[slow] = 0; 13 slow++; 14 } 15 } 16} 17
手推一遍最容易记住的状态变化
以 nums = [0, 1, 0, 3, 12] 为例:
| fast 位置 | 当前值 | slow 位置含义 | 操作 | 数组变化 |
|---|---|---|---|---|
| 0 | 0 | 下一个非零该放的位置 | 跳过 | [0, 1, 0, 3, 12] |
| 1 | 1 | 0 | 把 1 放到 nums[0],slow++ | [1, 1, 0, 3, 12] |
| 2 | 0 | 1 | 跳过 | [1, 1, 0, 3, 12] |
| 3 | 3 | 1 | 把 3 放到 nums[1],slow++ | [1, 3, 0, 3, 12] |
| 4 | 12 | 2 | 把 12 放到 nums[2],slow++ | [1, 3, 12, 3, 12] |
第一轮结束后,前 3 个位置已经是正确的非零序列 [1, 3, 12],所以只需要把后面补成 0,最终得到 [1, 3, 12, 0, 0]。
这个过程最关键的不是“把 0 移到后面”,而是始终维护一个不变量:[0, slow - 1] 永远是已经处理好的非零区间。
复杂度
- 时间复杂度:
O(n) - 空间复杂度:
O(1)
如果这是面试现场,你可以这样说
这题我会把它归类为快慢指针。fast 负责扫描数组,slow 指向下一个应该放非零元素的位置。扫描到非零元素时就覆盖到 slow 位置,最后把剩余位置补零。这样能在线性时间、常数空间内完成原地修改,而且能保持相对顺序不变。
6. 代表题精讲 2
题目
思路
这题是双指针里非常有代表性的 Medium。暴力做法是三重循环,复杂度 O(n^3),明显过高。
优化思路分三步:
- 先排序。
- 固定第一个数
nums[i]。 - 在剩余区间里用左右指针找两数之和等于
-nums[i]。
排序之后,左右指针可以利用和的大小移动:
- 如果和太小,左指针右移。
- 如果和太大,右指针左移。
- 如果刚好相等,记录答案后两边都跳过重复值。
去重是这题真正的难点:
i要去重。- 命中答案后,
left要跳过重复值。 - 命中答案后,
right也要跳过重复值。
Java 代码
1import java.util.ArrayList; 2import java.util.Arrays; 3import java.util.List; 4 5class Solution { 6 public List<List<Integer>> threeSum(int[] nums) { 7 Arrays.sort(nums); 8 List<List<Integer>> result = new ArrayList<>(); 9 10 for (int i = 0; i < nums.length - 2; i++) { 11 if (i > 0 && nums[i] == nums[i - 1]) { 12 continue; 13 } 14 15 int left = i + 1; 16 int right = nums.length - 1; 17 18 while (left < right) { 19 int sum = nums[i] + nums[left] + nums[right]; 20 21 if (sum < 0) { 22 left++; 23 } else if (sum > 0) { 24 right--; 25 } else { 26 result.add(Arrays.asList(nums[i], nums[left], nums[right])); 27 left++; 28 right--; 29 30 while (left < right && nums[left] == nums[left - 1]) { 31 left++; 32 } 33 while (left < right && nums[right] == nums[right + 1]) { 34 right--; 35 } 36 } 37 } 38 } 39 40 return result; 41 } 42} 43
手推一遍最能看懂去重和移动逻辑
以 nums = [-1, 0, 1, 2, -1, -4] 为例,先排序后得到:
[-4, -1, -1, 0, 1, 2]
第一轮固定 i = 0,也就是 nums[i] = -4:
left = 1,right = 5,和为-4 + (-1) + 2 = -3- 和太小,说明要变大,只能让
left++ - 后续无论怎么夹逼,这轮都找不到答案
第二轮固定 i = 1,也就是 nums[i] = -1:
left = 2,right = 5,和为-1 + (-1) + 2 = 0- 命中一个答案
[-1, -1, 2] - 然后
left++、right--,继续找这一轮里别的答案
接着:
left = 3,right = 4,和为-1 + 0 + 1 = 0- 再命中一个答案
[-1, 0, 1]
再往后 left 和 right 相遇,结束这一轮。
第三轮如果 i = 2,此时 nums[2] 还是 -1,和前一个固定值重复,所以必须直接跳过,否则会重复得到同样答案。
这段手推最值得你记住的是 3 个动作:
- 排序后再做夹逼。
- 和太小就移动左边,和太大就移动右边。
- 命中答案后,不只要收缩两边,还要跳过重复值。
复杂度
- 时间复杂度:
O(n^2) - 空间复杂度:
O(1),不计答案空间
如果这是面试现场,你可以这样说
这题我会先排序,然后把它转成“固定一个数,在剩余区间里做两数之和”的问题。排序后可以用左右指针根据和的大小移动,并且方便去重。整体复杂度从暴力的 O(n^3) 降到 O(n^2)。这题最关键的不是双指针本身,而是排序后的去重逻辑要完整。
7. 其余题模板与关键片段
26. 删除有序数组中的重复项
快慢指针维护“已去重区间”的结尾:
1int slow = 1; 2for (int fast = 1; fast < nums.length; fast++) { 3 if (nums[fast] != nums[fast - 1]) { 4 nums[slow] = nums[fast]; 5 slow++; 6 } 7} 8return slow; 9
11. 盛最多水的容器
核心不是枚举每一对,而是用左右指针逼近:
1int left = 0; 2int right = height.length - 1; 3int best = 0; 4while (left < right) { 5 int area = (right - left) * Math.min(height[left], height[right]); 6 best = Math.max(best, area); 7 if (height[left] < height[right]) { 8 left++; 9 } else { 10 right--; 11 } 12} 13
为什么移动较短板,是这题必须会讲的点。因为当前面积由短板决定,如果你保留较短板不动,只缩小宽度,那么面积上界只会更差;只有尝试换掉短板,才有机会让最小高度变高。
8. 边界、易混点与替代方案
快慢指针最容易错在哪
slow表示“结果区间的下一个位置”,不是“当前扫描位置”。- 原地覆盖后,别忘了处理尾部残留数据,比如
移动零的补零。 - 题目如果要求保持相对顺序,就不能随意交换元素。
左右指针最容易错在哪
- 不是看心情移动某一边,而是看“移动后有没有机会更接近目标”。
- 前提通常是有序,或者经过排序后具备单调性。
left < right和left <= right不能乱换,多数配对题用前者。
三数之和 为什么总有人写错
- 漏掉
i的去重。 - 命中答案后只移动一边,没有两边一起收缩。
- 命中答案后没有继续跳过重复值,导致重复答案。
这类题怎么判断值不值得用双指针
- 原地删除、移动、压缩:优先想快慢指针。
- 有序数组、配对、最优面积、和问题:优先想左右指针。
- 如果没有单调性、也不需要原地维护区间,双指针未必是最优路线。
9. 你学完后怎么验证自己真的会了
不要只看懂代码。更有效的做法,是用下面 3 组自测判断你是否真的掌握:
- 10 分钟内独立写出
移动零,并能解释slow为什么始终指向“下一个非零该放的位置”。 - 看到
三数之和时,能主动说出“先排序、固定一个数、左右夹逼、三层去重”。 - 看到
盛最多水的容器时,能解释为什么移动短板不会漏掉最优解。
如果你在自测时出现下面任意一种情况,就说明还没有真正掌握:
- 能看懂代码,但自己从空白开始写不出来。
- 知道是双指针,但不知道该用快慢还是左右。
- 代码大体能跑,但一到边界、去重、循环条件就不稳。
比较稳妥的过关标准是:你能在 15 分钟内做出一道基础双指针题,并且在 1 分钟内口述题型判断、指针含义、移动逻辑和复杂度。
10. 错题本记录方式
双指针题错题本重点记录:
- 我是快慢指针没想出来,还是左右指针不会证明。
- 这题的双指针不变量是什么。
- 我到底漏了哪个去重条件。
- 哪一步边界最容易写反,比如
left < right还是left <= right。
11. 适用范围与边界
- 本文默认你已经会 Java 数组遍历、循环和基础排序。
- 本文重点服务于 Java 实习面试、LeetCode 高频 Easy / Medium 和常见笔试基础题。
- 本文不展开证明型题解写法、竞赛技巧或更复杂的多指针变体。
如果你当前连“排序后为什么能用左右夹逼”都说不清,建议先拿 两数之和 II、移动零、盛最多水的容器 这几题把基本模型练顺,再去刷更复杂的变形题。
12. 面试前 3 分钟速记
- 原地覆盖、删除元素、移动元素,优先想快慢指针。
- 有序数组、配对、最优面积、和问题,优先想左右指针。
- 快慢指针先定义
slow表示什么。 - 左右指针先说明为什么移动某一边不会漏解。
三数之和记住:排序、固定一个数、左右夹逼、三层去重。
13. 结尾:把“会套模板”变成“会判断、会证明、会自测”
双指针之所以适合作为第二类高频题型,不是因为它代码短,而是因为它特别能训练你的题型判断力。你真正要练成的,不是背下两个模板,而是形成几个稳定动作:
- 先判断这题到底是快慢指针还是左右指针。
- 写代码前先定义每个指针分别表示什么。
- 移动指针时,能解释为什么不会漏答案。
- 做完题后,能反过来检查边界、去重和循环条件。
如果这几个动作稳定下来,后面的滑动窗口、链表、二分和回溯,你都会更容易进入状态。
感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!
《【Java实习面试算法冲刺】双指针》 是转载文章,点击查看原文。

