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

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

为什么快慢指针是链表问题的 “最优解”?

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

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

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

快慢指针 3 大核心用法:原理 + 代码 + 场景全解析

(一)用法 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→2
        System.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
    }
}

(三)用法 3:寻找链表的倒数第 K 个节点(技巧性拉满)

1. 问题场景

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

2. 原理分析

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

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

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

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

public class KthFromEnd {
    public ListNode getKthFromEnd(ListNode head, int k) {
        // 边界条件:空链表或K≤0,返回null
        if (head == null || k <= 0) {
            return null;
        }
        ListNode slow = head;
        ListNode fast = head;
        // 第一步:快指针先移动K步
        for (int i = 0; i < k; i++) {
            // 若快指针提前到达null,说明K大于链表长度
            if (fast == null) {
                return null;
            }
            fast = fast.next;
        }
        // 第二步:快慢指针同时移动,直到快指针为null
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        // 慢指针即为倒数第K个节点
        return slow;
    }

    // 测试案例
    public static void main(String[] args) {
        KthFromEnd solution = new KthFromEnd();
        
        // 构建链表:1→2→3→4→5
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);
        
        // 案例1:倒数第2个节点
        System.out.println("案例1(倒数第2个):" + solution.getKthFromEnd(head, 2).val);  // 输出4
        // 案例2:倒数第5个节点
        System.out.println("案例2(倒数第5个):" + solution.getKthFromEnd(head, 5).val);  // 输出1
        // 案例3:K=6(大于链表长度)
        System.out.println("案例3(K=6):" + solution.getKthFromEnd(head, 6));  // 输出null
    }
}

5 道大厂面试真题实战:从基础到进阶

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

真题 1:判断链表是否有环(字节跳动 - 初级)

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

易错点

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

真题 2:环形链表 II(阿里 - 中级)

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

解法思路

  1. 先用快慢指针判断链表是否有环,若无环返回 null。
  2. 若有环,将慢指针重置为 head,快指针保持在相遇点。
  3. 两者以 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;
    }
    // 若无环,返回null
    if (fast == null || fast.next == null) return null;
    // 第二步:找环入口
    slow = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}

真题 3:回文链表(腾讯 - 中级)

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

解法思路

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

关键步骤:反转后半部分链表的代码实现。

真题 4:删除链表的倒数第 N 个节点(美团 - 中级)

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

解法思路

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

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

真题 5:链表的中间节点(百度 - 初级)

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

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

避坑指南:新手常犯的 4 个错误

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

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

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

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

总结

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

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

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

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

原文链接:,转发请注明来源!