双指针 - 快慢指针

介绍

快慢指针用于链表或数组中通过“速度差”获取结构信息。简单来说,就是用两个指针同时遍历,慢指针走得慢、快指针走得快,通过二者的相遇、交汇或间隔距离来判断环、找中点、倒数第 N 个节点或检测回文等。这样算法通常也能做到线性复杂度。

1. 判环(环的存在性)

LeetCode 例子:141. Linked List Cycle

题意:判断链表中是否存在环。

思路要点

  • 定义 快慢指针:慢指针一次走 1 步,快指针一次走 2 步;
  • 若链表存在环,快慢指针最终会在环内相遇;
  • 若链表无环,快指针先到达末尾。

核心代码

ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow == fast) return true; // 相遇则有环
}
return false; // 无环

完整函数写法

public boolean hasCycle(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) return true;
    }
    return false;
}

2. 找环入口

LeetCode 例子:142. Linked List Cycle II

题意:返回链表环的起点,如果无环返回 null。

思路要点

  • 首先使用快慢指针判断环是否存在;
  • 相遇后,将一个指针重新指向链表头,两指针同步移动,每次走 1 步;
  • 再次相遇点即为环入口。

核心代码

ListNode slow = head, fast = head;
// 判断是否有环
do {
    if (fast == null || fast.next == null) return null;
    slow = slow.next;
    fast = fast.next.next;
} while (slow != fast);

// 找环入口
slow = head;
while (slow != fast) {
    slow = slow.next;
    fast = fast.next;
}
return slow;

完整函数写法

public ListNode detectCycle(ListNode head) {
    ListNode slow = head, fast = head;
    // 判环
    do {
        if (fast == null || fast.next == null) return null;
        slow = slow.next;
        fast = fast.next.next;
    } while (slow != fast);

    // 找环入口
    slow = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}

3. 链表中点查找

LeetCode 例子:876. Middle of the Linked List

题意:找到链表的中点节点(偶数长度返回中间偏右节点)。

思路要点

  • 快指针每次走 2 步,慢指针每次走 1 步;
  • 快指针到达末尾时,慢指针恰好在中点位置。

核心代码

ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
}
return slow;

完整函数写法

public ListNode middleNode(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

4. 倒数第 N 个节点

LeetCode 例子:19. Remove Nth Node From End of List

题意:返回链表倒数第 N 个节点(或删除该节点)。

思路要点

  • 快指针先走 N 步,使快慢指针间隔 N 个节点;
  • 然后快慢指针同时走,直到快指针到达末尾;
  • 慢指针指向的就是倒数第 N 个节点前一个节点(方便删除操作)。

核心代码(删除倒数第 N 个节点)

ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode slow = dummy, fast = dummy;

// 快指针先走 N 步
for (int i = 0; i < n; i++) fast = fast.next;

// 同时移动
while (fast.next != null) {
    slow = slow.next;
    fast = fast.next;
}

// 删除节点
slow.next = slow.next.next;
return dummy.next;

完整函数写法

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode slow = dummy, fast = dummy;
    for (int i = 0; i < n; i++) fast = fast.next;
    while (fast.next != null) {
        slow = slow.next;
        fast = fast.next;
    }
    slow.next = slow.next.next;
    return dummy.next;
}

5. 回文检查(数组/链表)

LeetCode 例子:234. Palindrome Linked List

题意:判断链表是否是回文结构。

思路要点

  • 用快慢指针找到链表中点;
  • 反转中点后的链表;
  • 两段链表逐节点比较,判断是否回文;
  • 可选:恢复链表结构。

核心代码

// 找中点
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
}

// 反转后半段
ListNode prev = null, curr = slow;
while (curr != null) {
    ListNode next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
}

// 比较
ListNode p1 = head, p2 = prev;
while (p2 != null) {
    if (p1.val != p2.val) return false;
    p1 = p1.next;
    p2 = p2.next;
}
return true;

完整函数写法

public boolean isPalindrome(ListNode head) {
    if (head == null || head.next == null) return true;

    // 找中点
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }

    // 反转后半段
    ListNode prev = null, curr = slow;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }

    // 比较两段链表
    ListNode p1 = head, p2 = prev;
    while (p2 != null) {
        if (p1.val != p2.val) return false;
        p1 = p1.next;
        p2 = p2.next;
    }
    return true;
}

总结:快慢指针

快慢指针类型 核心目标 指针变化 状态维护 统计方式 面试高频题
判环(Cycle Detection) 判断链表是否有环 慢指针每次走 1 步,快指针每次走 2 步,相遇则有环 无需额外结构,仅指针移动 相遇即判定有环 LC 141, Linked List Cycle
找环入口(Cycle Entry) 找出链表环的起始节点 快慢指针相遇后,一个从头开始,一个从相遇点,步数同步推进,直到相遇 指针移动和相遇条件 相遇节点即环入口 LC 142, Linked List Cycle II
链表中点 找到链表中点(偶数取上中/下中根据需求) 慢指针 1 步,快指针 2 步 无需额外维护 慢指针到达即中点 LC 876, Middle of Linked List
倒数第 N 个节点 找到链表倒数第 N 个节点 快指针先走 N 步,慢指针随后同步推进 记录快慢指针位置 快指针到末尾时慢指针即目标 LC 19, Remove Nth Node From End of List
回文检查 判断链表或数组是否回文 快慢指针找中点,慢指针反转前半部分或记录栈 链表反转或栈存储前半部分 中点后对比两段元素 LC 234, Palindrome Linked List



    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • 双指针 - 左右指针
  • 双指针 - 滑动窗口
  • Todo List
  • Twitter Notification and Real-Time Push System Design Detailed Guide
  • Twitter Database Read and Write and Sharding System Design Detailed Guide