吃透链表快慢指针:3 大核心用法 + 5 道大厂面试真题,看完直接上手

B站影视 欧美电影 2025-09-05 17:44 1

摘要:在互联网大厂算法面试中,链表题型的出现频率常年稳居前 5,而快慢指针作为解决链表问题的 “万能钥匙”,更是高频考点中的重中之重。根据牛客网 2025 年 Q2《算法面试趋势报告》显示,在字节跳动、阿里、腾讯等大厂的链表面试题中,涉及快慢指针的题目占比高达 68

在互联网大厂算法面试中,链表题型的出现频率常年稳居前 5,而快慢指针作为解决链表问题的 “万能钥匙”,更是高频考点中的重中之重。根据牛客网 2025 年 Q2《算法面试趋势报告》显示,在字节跳动、阿里、腾讯等大厂的链表面试题中,涉及快慢指针的题目占比高达 68%,其中 “判断链表有环”“寻找链表中点” 等题型的重复考察率超过 40%。但不少面试者因对原理理解不深、代码细节把控不足,常常在这类基础题上栽跟头。今天这篇文章,就带大家彻底吃透快慢指针的核心逻辑,从概念到实现,从真题到避坑,帮你轻松应对大厂面试中的链表考点。

在聊具体用法前,我们先搞懂一个关键问题:为什么解决链表问题非要用快慢指针?这还要从链表的存储特性说起。

链表不同于数组,它的节点通过指针串联,无法像数组那样通过下标直接访问元素。如果想用常规方法解决 “找中点”“找倒数第 K 个节点” 这类问题,通常需要先遍历一次链表统计长度,再遍历第二次定位目标节点 —— 这种 “两次遍历” 的方案时间复杂度虽也是 O (n),但在面试中会被认为 “不够优雅”,而快慢指针能实现一次遍历完成目标,更符合大厂对算法效率与代码简洁性的要求。

所谓快慢指针,本质是定义两个起始位置相同的指针,通过设置不同的移动步长(通常慢指针一次 1 步,快指针一次 2 步),利用两者的 “速度差” 实现对链表的高效遍历。这种思路不仅适用于单链表,在双向链表、循环链表中也能灵活运用,是算法面试中的 “性价比之王” 技巧。

(一)用法 1:判断链表是否有环(高频中的高频)

1. 问题场景

给定一个单链表,判断链表中是否存在环。例如:链表 A→B→C→B,其中 C 的 next 指向 B,形成一个环,此时需返回 true;若链表为 A→B→C→null,则返回 false。

2. 原理分析

这是快慢指针最经典的应用,核心逻辑类似 “追及问题”:若链表无环,快指针会先到达链表尾部(指向 null);若链表有环,快指针会在环内循环移动,最终追上慢指针(两者指向同一个节点)。这里要注意两个关键细节:

快指针的移动步长必须是慢指针的 2 倍吗?其实不一定,只要快指针比慢指针快即可(如快指针 3 步、慢指针 1 步),但 2 倍步长是最常用、代码最简洁的方案。循环终止条件是什么?必须先判断fast != null和fast.next != null,再移动指针,避免出现空指针异常(NPE)。

3. 完整代码实现(Java 版)

// 定义链表节点类class ListNode {int val;ListNode next;ListNode(int x) {val = x;next = null;}}public class LinkedListCycle {// 判断链表是否有环的核心方法public boolean hasCycle(ListNode head) {// 边界条件:空链表或只有一个节点,必然无环if (head == null || head.next == null) {return false;}// 初始化快慢指针,均指向头节点ListNode slow = head;ListNode fast = head;// 循环条件:快指针未到达尾部while (fast != null && fast.next != null) {slow = slow.next; // 慢指针走1步fast = fast.next.next; // 快指针走2步// 若相遇,说明有环if (slow == fast) {return true;}}// 循环结束仍未相遇,无环return false;}// 测试案例public static void main(String args) {LinkedListCycle solution = new LinkedListCycle;// 案例1:有环链表ListNode head1 = new ListNode(3);ListNode node2 = new ListNode(2);ListNode node3 = new ListNode(0);ListNode node4 = new ListNode(-4);head1.next = node2;node2.next = node3;node3.next = node4;node4.next = node2; // 形成环:-4→2System.out.println("案例1(有环):" + solution.hasCycle(head1)); // 输出true// 案例2:无环链表ListNode head2 = new ListNode(1);head2.next = new ListNode(2);System.out.println("案例2(无环):" + solution.hasCycle(head2)); // 输出false}}

4. 面试延伸问题

大厂面试中常在此基础上追问:“若链表有环,如何找到环的入口节点?” 解法是:当快慢指针相遇后,将慢指针重置为头节点,然后两者以相同步长(每次 1 步)移动,再次相遇的节点即为环的入口。

(二)用法 2:寻找链表的中点(二分法基础)

1. 问题场景

给定一个单链表,返回链表的中间节点。若链表长度为偶数,返回第二个中间节点(如链表 1→2→3→4,返回 3);若为奇数,返回正中间节点(如 1→2→3,返回 2)。

2. 原理分析

利用快慢指针的速度差,当快指针到达链表尾部时,慢指针恰好停在中点位置。这里的关键是 “快指针的终止位置”:

若快指针移动到fast.next == null(奇数长度),此时慢指针指向正中间节点。若快指针移动到fast == null(偶数长度),此时慢指针指向第二个中间节点。

这种特性使其成为 “链表二分法” 的基础,例如在 “链表归并排序”“判断回文链表” 等问题中都有重要应用。

3. 完整代码实现(Java 版)

public class MiddleNode {public ListNode middleNode(ListNode head) {if (head == null || head.next == null) {return head;}ListNode slow = head;ListNode fast = head;// 循环条件:快指针未到达尾部while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}// 循环结束,slow即为中点return slow;}// 测试案例public static void main(String args) {MiddleNode solution = new MiddleNode;// 案例1:奇数长度链表(1→2→3→4→5)ListNode head1 = new ListNode(1);head1.next = new ListNode(2);head1.next.next = new ListNode(3);head1.next.next.next = new ListNode(4);head1.next.next.next.next = new ListNode(5);System.out.println("案例1中点值:" + solution.middleNode(head1).val); // 输出3// 案例2:偶数长度链表(1→2→3→4)ListNode head2 = new ListNode(1);head2.next = new ListNode(2);head2.next.next = new ListNode(3);head2.next.next.next = new ListNode(4);System.out.println("案例2中点值:" + solution.middleNode(head2).val); // 输出3}}

1. 问题场景

给定一个单链表,找出链表的倒数第 K 个节点。例如:链表 1→2→3→4→5,倒数第 2 个节点为 4;若 K=5,则返回 1。

2. 原理分析

常规思路是 “先遍历统计长度,再遍历定位”,但快慢指针能实现 “一次遍历”:

让快指针先向前移动 K 步,此时快慢指针之间间隔 K 个节点。然后快慢指针同时以 1 步 / 次的速度移动,直到快指针到达链表尾部(指向 null)。此时慢指针指向的节点即为倒数第 K 个节点。

这里需要特别注意边界条件处理:当 K 大于链表长度时,应返回 null;当 K=0 或负数时,也需返回 null(符合实际业务逻辑)。

3. 完整代码实现(Java 版)

public class KthFromEnd {public ListNode getKthFromEnd(ListNode head, int k) {// 边界条件:空链表或K≤0,返回nullif (head == null || k

掌握了核心用法后,我们通过 5 道大厂真题巩固练习,这些题目均来自 2025 年字节、阿里、美团的真实面试场景。

题目:给你一个链表的头节点 head,判断链表中是否有环。(同用法 1,此处略去代码,重点看面试中的易错点)

易错点

忘记处理空链表或单节点链表的边界条件。循环条件写成while (fast.next != null && fast != null),会导致空指针异常(当 fast 为 null 时,fast.next 会报错)。快慢指针初始化时,让 fast 直接指向 head.next,导致判断逻辑偏差。

题目:给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,则返回 null。

解法思路

先用快慢指针判断链表是否有环,若无环返回 null。若有环,将慢指针重置为 head,快指针保持在相遇点。两者以 1 步 / 次的速度移动,再次相遇的节点即为环的入口。

代码核心片段

public ListNode detectCycle(ListNode head) {if (head == null || head.next == null) return null;ListNode slow = head, fast = head;// 第一步:判断是否有环while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) break;}// 若无环,返回nullif (fast == null || fast.next == null) return null;// 第二步:找环入口slow = head;while (slow != fast) {slow = slow.next;fast = fast.next;}return slow;}

题目:给你一个单链表的头节点 head,请你判断该链表是否为回文链表。

用快慢指针找到链表中点。反转中点后的后半部分链表。对比前半部分与反转后的后半部分,若全部相同则为回文。

题目:给你一个链表,删除链表的倒数第 n 个节点,并且返回链表的头节点。

解法思路

用快慢指针找到倒数第 n 个节点的前一个节点(避免删除头节点时的边界问题)。改变前一个节点的 next 指针,跳过倒数第 n 个节点。

技巧:可创建一个 “虚拟头节点”(dummy node),让慢指针初始指向 dummy,避免处理头节点删除的特殊情况。

题目:同用法 2,此处重点考察代码的简洁性与边界处理能力。

面试加分项:能主动说明 “若需返回第一个中间节点(偶数长度时),如何修改代码”(将循环条件改为while (fast.next != null && fast.next.next != null))。

边界条件遗漏:忘记处理空链表、单节点链表、K 大于链表长度等情况,导致代码在特殊测试用例下崩溃。

指针移动顺序错误:在判断相遇前先移动指针,或移动顺序颠倒,导致逻辑错误。

空指针异常:循环条件未先判断fast != null就访问fast.next,尤其在偶数长度链表中容易出现。

步长设置不当:在判断环的问题中,将快指针步长设为 3 步及以上,虽然理论可行,但会增加代码复杂度,且容易出现 “跳过相遇点” 的情况,面试中建议优先用 2 倍步长。

吃透原理:记住 “速度差” 是快慢指针的核心,不同用法的本质是利用速度差实现 “一次遍历定位目标”。

多写代码:将 3 大核心用法的代码手写 3-5 遍,确保能脱离参考独立完成,重点关注边界条件处理。

真题实战:完成本文中的 5 道真题后,可在 LeetCode 上搜索 “链表”“快慢指针” 标签的题目(推荐题号:141、142、876、19、234),强化实战能力。

算法面试拼的不是天赋,而是方法与练习。快慢指针作为入门级的技巧,只要掌握原理、多练多总结,就能成为你面试中的 “得分利器”。最后留一个小问题:如何用快慢指针判断两个单链表是否相交?欢迎在评论区留下你的思路,点赞过 500,下期我们详细讲解!

来源:从程序员到架构师一点号

相关推荐