【Java实习面试算法冲刺】双指针

作者:ZenithSourceQuest日期:2026/7/6

第2类题型:双指针

为什么双指针题看起来不难,你一到面试就容易写乱

很多同学第一次刷双指针时,会觉得这类题比哈希表还“直观”。因为代码通常不长,变量也常常只有 leftrightslowfast 四个名字。但真正到了面试现场,双指针反而很容易暴露出两类问题:

  • 你会套模板,但说不清两个指针各自代表什么。
  • 你知道要移动某一边,却解释不出“为什么这样移动不会漏解”。
  • 你能把 三数之和 写个大概,却总在去重和边界上翻车。
  • 你把“会写代码”当成“理解题型”,结果一换题面就不稳。

如果你现在正处在“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. 核心知识点

双指针本质上有两类模型:

  • 快慢指针:常用于原地覆盖、去重、移动元素。
  • 左右指针:常用于有序数组、逼近目标值、利用单调性缩小范围。

识别信号很常见:

  • 数组要原地修改。
  • 已排序,或者可以先排序。
  • 要找两边夹逼、最优面积、和问题。
  • 存在去重细节。

最基础的快慢指针模板是:

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]
  • 内层用 leftright 在剩余区间里根据和的大小夹逼。

2. 这类题在面试里考什么

双指针题看起来不难,但特别能暴露“会背模板”和“真正理解不变量”的差距。

面试官通常在看:

  • 你是否能解释两个指针的含义。
  • 你为什么移动左边而不是右边。
  • 你如何证明不会漏掉最优解。
  • 你是否处理好了去重和边界。

很多候选人会写出一个大概对的结构,但一旦被追问“为什么这一步能这么移”,就说不清楚。这正是双指针题的区分度。


3. 高频题清单

题目来源难度高频属性
283. 移动零LeetCode 热题 100Easy面试高频
26. 删除有序数组中的重复项面试经典 150Easy基础高频
11. 盛最多水的容器LeetCode 热题 100Medium高频 Medium
15. 三数之和LeetCode 热题 100Medium真实面经高频

4. 这类题最容易犯的 3 个错误

  1. 双指针能做的题,还在用多重循环硬枚举。
  2. 左右指针会移动,但说不清为什么这么移动不会漏解。
  3. 三数之和 这类题排序之后,去重细节没有写全。

5. 代表题精讲 1

题目

283. 移动零

思路

这题是最典型的快慢指针。要求把所有 0 移到数组末尾,同时保持非零元素的相对顺序。

可以把 slow 理解为“下一个非零元素应该放的位置”,把 fast 理解为“当前正在扫描的位置”。

做法:

  1. fast 从左到右扫描。
  2. 如果 nums[fast] != 0,就把它放到 nums[slow],然后 slow++
  3. 第一轮结束后,[0, slow - 1] 已经是所有非零元素。
  4. 再把 [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 位置含义操作数组变化
00下一个非零该放的位置跳过[0, 1, 0, 3, 12]
110把 1 放到 nums[0],slow++[1, 1, 0, 3, 12]
201跳过[1, 1, 0, 3, 12]
331把 3 放到 nums[1],slow++[1, 3, 0, 3, 12]
4122把 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),明显过高。

优化思路分三步:

  1. 先排序。
  2. 固定第一个数 nums[i]
  3. 在剩余区间里用左右指针找两数之和等于 -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 = 1right = 5,和为 -4 + (-1) + 2 = -3
  • 和太小,说明要变大,只能让 left++
  • 后续无论怎么夹逼,这轮都找不到答案

第二轮固定 i = 1,也就是 nums[i] = -1

  • left = 2right = 5,和为 -1 + (-1) + 2 = 0
  • 命中一个答案 [-1, -1, 2]
  • 然后 left++right--,继续找这一轮里别的答案

接着:

  • left = 3right = 4,和为 -1 + 0 + 1 = 0
  • 再命中一个答案 [-1, 0, 1]

再往后 leftright 相遇,结束这一轮。

第三轮如果 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 < rightleft <= right 不能乱换,多数配对题用前者。

三数之和 为什么总有人写错

  • 漏掉 i 的去重。
  • 命中答案后只移动一边,没有两边一起收缩。
  • 命中答案后没有继续跳过重复值,导致重复答案。

这类题怎么判断值不值得用双指针

  • 原地删除、移动、压缩:优先想快慢指针。
  • 有序数组、配对、最优面积、和问题:优先想左右指针。
  • 如果没有单调性、也不需要原地维护区间,双指针未必是最优路线。

9. 你学完后怎么验证自己真的会了

不要只看懂代码。更有效的做法,是用下面 3 组自测判断你是否真的掌握:

  1. 10 分钟内独立写出 移动零,并能解释 slow 为什么始终指向“下一个非零该放的位置”。
  2. 看到 三数之和 时,能主动说出“先排序、固定一个数、左右夹逼、三层去重”。
  3. 看到 盛最多水的容器 时,能解释为什么移动短板不会漏掉最优解。

如果你在自测时出现下面任意一种情况,就说明还没有真正掌握:

  • 能看懂代码,但自己从空白开始写不出来。
  • 知道是双指针,但不知道该用快慢还是左右。
  • 代码大体能跑,但一到边界、去重、循环条件就不稳。

比较稳妥的过关标准是:你能在 15 分钟内做出一道基础双指针题,并且在 1 分钟内口述题型判断、指针含义、移动逻辑和复杂度。

10. 错题本记录方式

双指针题错题本重点记录:

  • 我是快慢指针没想出来,还是左右指针不会证明。
  • 这题的双指针不变量是什么。
  • 我到底漏了哪个去重条件。
  • 哪一步边界最容易写反,比如 left < right 还是 left <= right

11. 适用范围与边界

  • 本文默认你已经会 Java 数组遍历、循环和基础排序。
  • 本文重点服务于 Java 实习面试、LeetCode 高频 Easy / Medium 和常见笔试基础题。
  • 本文不展开证明型题解写法、竞赛技巧或更复杂的多指针变体。

如果你当前连“排序后为什么能用左右夹逼”都说不清,建议先拿 两数之和 II移动零盛最多水的容器 这几题把基本模型练顺,再去刷更复杂的变形题。


12. 面试前 3 分钟速记

  • 原地覆盖、删除元素、移动元素,优先想快慢指针。
  • 有序数组、配对、最优面积、和问题,优先想左右指针。
  • 快慢指针先定义 slow 表示什么。
  • 左右指针先说明为什么移动某一边不会漏解。
  • 三数之和 记住:排序、固定一个数、左右夹逼、三层去重。

13. 结尾:把“会套模板”变成“会判断、会证明、会自测”

双指针之所以适合作为第二类高频题型,不是因为它代码短,而是因为它特别能训练你的题型判断力。你真正要练成的,不是背下两个模板,而是形成几个稳定动作:

  • 先判断这题到底是快慢指针还是左右指针。
  • 写代码前先定义每个指针分别表示什么。
  • 移动指针时,能解释为什么不会漏答案。
  • 做完题后,能反过来检查边界、去重和循环条件。

如果这几个动作稳定下来,后面的滑动窗口、链表、二分和回溯,你都会更容易进入状态。


感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!

下一期:【Java实习面试算法冲刺】滑动窗口


【Java实习面试算法冲刺】双指针》 是转载文章,点击查看原文


相关推荐


论虚拟线程与 Kotlin IO 协程:资源开销、时长、高并发表现及适用场景与技术选型思考
zimoyin2026/6/28

在现代并发编程中,虚拟线程(由 Java 20+ 引入)和 Kotlin IO 协程(基于 Dispatchers.IO)是两种高效处理异步任务的技术框架。在资源开销、时长(特别是长时间 IO)、高并发场景表现、以及何时选择合适方案等方面各有特点。本文将逐一展开对比分析。 1. 资源开销对比 虚拟线程和协程的核心开销差异源于其底层设计原理: 维度虚拟线程Kotlin IO 协程关键差异内存开销固定栈机制:默认约 1MB1\text{MB}1MB 占⽤[注1]动态内存分配:2KB∼512KB2


当 AI 学会「自己催自己」:对 Loop Engineering 的理解与思考
莫西很trouble2026/6/19

先说一个让我「咯噔」一下的瞬间 前段时间看到 Claude Code 负责人 Boris Cherny 说了句话,大意是:我已经不写 Prompt 了,我只写 Loop 我的第一反应是:啊?Prompt 不是刚学会怎么写好吗?怎么就又过时了? 但仔细想了下这句话背后的意思,突然意识到它不是在讲什么新技术,而是在讲一个我们早就该意识到的问题 ——如果每次用 AI,你都要在它身边喊「继续」「还是报错」「你改了啥」「回滚」,那说明你其实不是在用工具,你是在当监工 而 Loop Engineer


限流:从单机QPS计数器到分布式三层防御体系
程序员小策2026/6/11

大家好,我是程序员小策。 先说一个反直觉的事实:加了限流之后,你系统的成功请求数量反而可能变多。 听起来很荒诞对吧?限流的字面意思就是"拦住一部分请求",拦住了怎么可能变多? 但数据不会骗人: 场景总请求数成功数成功率不限流5000000%加了限流50000500010% 不限流的时候,50000 个请求全部涌入数据库,连接池打满,超时重试又制造了一倍流量,雪崩导致所有接口全部失败——包括那些只想来浏览商品页的正常用户。 加了限流之后,50000 个请求里被拦掉了 45000 个,但这


Java学习笔记之泛型
飞翔网2026/6/4

前言 写 Java 代码时,你一定见过 List<String>、Map<Integer, String> 这种尖括号写法。这就是泛型(Generics)——Java 5 引入的最重要的语言特性之一。在没有泛型的时代,集合里塞什么都可以,取出来必须强制转型,稍不注意就 ClassCastException(类转型异常)。泛型的出现让类型安全从运行时提到了编译期。 但这只是泛型的冰山一角。泛型真正的难点在于类型擦除、通配符、PECS 原则——理解了这些,你才算真正掌握了泛型。 一、概念:什么是泛


【SpringBoot+Elasticsearch 内容搜索系统实战】:架构设计与全流程实现
fengxin_rou2026/5/29

🔥你好我是fengxin_rou这是我的个人主页fengxin_rou的主页 ❄️欢迎查看我的专栏我的专栏 《Java后端学习》、《JAVASE基础》、《JUC并发》、《redis》、《JVM虚拟机》、《MYSQL》、《黑马点评》、《rabbitmq》、《JavaWeb+AI的talis学习系统》、《苍穹外卖》 目录 前言 一、Elasticsearch 索引设计与初始化 1.1 核心概念类比 1.2 索引初始化实现 1.3 字段设计要点 二、搜索索引数据写入与同步机


深入理解 Kotlin 协程 (六):进退有度,解密协程取消响应与异常分发机制
雨白2026/5/7

协程的取消机制 取消协程需要协程内部配合,这点和线程一样,本质上也是协作式的取消,就是将状态设置为取消,协程内部根据状态的变化来响应。 完善 Job 的状态流转与取消通知 我们基于上一篇博客中的代码,来完善协程的取消逻辑。 首先支持协程取消回调的注册: // [AbstractCoroutine.kt] override fun invokeOnCancel(onCancel: OnCancel): Disposable { // 1. 创建回调包装对象,以便后续可以手动解绑 v


Flink+Kafka:数据流处理实战指南
渣渣盟2026/4/27

目录 代码结构 代码解析 (1) 主程序入口 (2) 定义数据流 (3) 使用旧版 Kafka Sink (4) 使用新版 Kafka Sink (5) 将数据写入 Kafka (6) 执行任务 代码优化 交付保证 异常处理 动态 Topic 优化后的代码 这段代码展示了如何使用 Apache Flink 将数据流写入 Kafka,并提供了两种不同的 Kafka Sink 实现方式。以下是对代码的详细解析和说明: 代码结构 包声明:package sink


OpenClaw——让龙虾像真人一样控制桌面的SKILL(macOS版)
KD2026/4/19

一、背景 工作中要做一个桌面控制相关需求,试了下ClawHub现有Desktop Control skill,发现都有一些不好用的地方,或者与macOS系统不够适配,因此写了一个新skill供大家使用和交流 二、概述 这个Skill主要链路如下: 三、具体步骤实现拆解 1.初始化 这一步是最关键的,也是很多现有skill缺失的一步。第一版本先只做Retina屏兼容 在 macOS 上,即使截图和点击都用 Python,也仍然需要先确认几件事情: 截图图像尺寸是多少 屏幕逻辑尺寸是多少 鼠标


AI Agent 智能体开发入门:AutoGen 多智能体协作实战教程
Halcyon.平安2026/4/10

本文通过 AutoGen 框架,从单智能体到多智能体协作,循序渐进地讲解如何构建 AI Agent 系统,包含完整的代码示例和架构设计。 1. 多智能体协作架构 #mermaid-svg-TX83Bcl6adrsEqiY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}


Claude Code 防上下文爆炸:源码级深度解析
lizhongxuan2026/4/2

基于 Claude Code v2.1.88 源码还原分析。本文从源码层面拆解 Claude Code 如何在长对话中管理上下文窗口,防止 token 爆炸,同时保持用户意图不被稀释。 问题:为什么上下文会爆炸? Claude Code 是一个 agentic coding 工具。一次典型的编码会话中,模型会: 读取十几个文件(每个几百到几千行) 执行 shell 命令并获取输出 搜索代码库(grep/glob 结果可能很大) 编辑文件并查看 diff 调用子 agent 处理子任务 每一

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读