第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/)
- 8. 边界、易混点与替代方案
- 快慢指针最容易错在哪
- 左右指针最容易错在哪
- `三数之和` 为什么总有人写错
- 这类题怎么判断值不值得用双指针
- 9. 你学完后怎么验证自己真的会了
- 10. 错题本记录方式
- 11. 适用范围与边界
- 12. 面试前 3 分钟速记
- 13. 结尾:把“会套模板”变成“会判断、会证明、会自测”
1. 核心知识点
双指针本质上有两类模型:
- 快慢指针:常用于原地覆盖、去重、移动元素。
- 左右指针:常用于有序数组、逼近目标值、利用单调性缩小范围。
识别信号很常见:
- 数组要原地修改。
- 已排序,或者可以先排序。
- 要找两边夹逼、最优面积、和问题。
- 存在去重细节。
最基础的快慢指针模板是:
intslow=0;for(intfast=0;fast<nums.length;fast++){if(满足保留条件){nums[slow]=nums[fast];slow++;}}左右指针模板则更像:
intleft=0;intright=nums.length-1;while(left<right){// 根据条件移动 left 或 right}真正决定你能不能写对的,不是模板本身,而是你能不能说清楚两个指针各自代表什么,以及每一步移动为什么不会漏答案。
动画辅助理解:固定一个数,再让左右指针夹逼
双指针最适合用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
题目
283. 移动零
思路
这题是最典型的快慢指针。要求把所有0移到数组末尾,同时保持非零元素的相对顺序。
可以把slow理解为“下一个非零元素应该放的位置”,把fast理解为“当前正在扫描的位置”。
做法:
- 用
fast从左到右扫描。 - 如果
nums[fast] != 0,就把它放到nums[slow],然后slow++。 - 第一轮结束后,
[0, slow - 1]已经是所有非零元素。 - 再把
[slow, n - 1]全部填成0。
Java 代码
classSolution{publicvoidmoveZeroes(int[]nums){intslow=0;for(intfast=0;fast<nums.length;fast++){if(nums[fast]!=0){nums[slow]=nums[fast];slow++;}}while(slow<nums.length){nums[slow]=0;slow++;}}}手推一遍最容易记住的状态变化
以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
题目
15. 三数之和
思路
这题是双指针里非常有代表性的 Medium。暴力做法是三重循环,复杂度O(n^3),明显过高。
优化思路分三步:
- 先排序。
- 固定第一个数
nums[i]。 - 在剩余区间里用左右指针找两数之和等于
-nums[i]。
排序之后,左右指针可以利用和的大小移动:
- 如果和太小,左指针右移。
- 如果和太大,右指针左移。
- 如果刚好相等,记录答案后两边都跳过重复值。
去重是这题真正的难点:
i要去重。- 命中答案后,
left要跳过重复值。 - 命中答案后,
right也要跳过重复值。
Java 代码
importjava.util.ArrayList;importjava.util.Arrays;importjava.util.List;classSolution{publicList<List<Integer>>threeSum(int[]nums){Arrays.sort(nums);List<List<Integer>>result=newArrayList<>();for(inti=0;i<nums.length-2;i++){if(i>0&&nums[i]==nums[i-1]){continue;}intleft=i+1;intright=nums.length-1;while(left<right){intsum=nums[i]+nums[left]+nums[right];if(sum<0){left++;}elseif(sum>0){right--;}else{result.add(Arrays.asList(nums[i],nums[left],nums[right]));left++;right--;while(left<right&&nums[left]==nums[left-1]){left++;}while(left<right&&nums[right]==nums[right+1]){right--;}}}}returnresult;}}手推一遍最能看懂去重和移动逻辑
以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. 删除有序数组中的重复项
快慢指针维护“已去重区间”的结尾:
intslow=1;for(intfast=1;fast<nums.length;fast++){if(nums[fast]!=nums[fast-1]){nums[slow]=nums[fast];slow++;}}returnslow;11. 盛最多水的容器
核心不是枚举每一对,而是用左右指针逼近:
intleft=0;intright=height.length-1;intbest=0;while(left<right){intarea=(right-left)*Math.min(height[left],height[right]);best=Math.max(best,area);if(height[left]<height[right]){left++;}else{right--;}}为什么移动较短板,是这题必须会讲的点。因为当前面积由短板决定,如果你保留较短板不动,只缩小宽度,那么面积上界只会更差;只有尝试换掉短板,才有机会让最小高度变高。
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. 结尾:把“会套模板”变成“会判断、会证明、会自测”
双指针之所以适合作为第二类高频题型,不是因为它代码短,而是因为它特别能训练你的题型判断力。你真正要练成的,不是背下两个模板,而是形成几个稳定动作:
- 先判断这题到底是快慢指针还是左右指针。
- 写代码前先定义每个指针分别表示什么。
- 移动指针时,能解释为什么不会漏答案。
- 做完题后,能反过来检查边界、去重和循环条件。
如果这几个动作稳定下来,后面的滑动窗口、链表、二分和回溯,你都会更容易进入状态。
感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!
下一期:疯狂码字ing……