Loading......

文章背景图

算法 - 快慢指针专项

2026-04-16
33
-
- 分钟
|

核心:快慢指针法通过设置两个同向移动但速度不同的指针,解决链表中的环检测、中点查找、重排等问题。其本质是利用相对运动特性,让两指针在特定条件下相遇。


一、基础知识点

1. 链表中点查找(快慢指针基础)

问题定义

给定单链表,找到链表的中间节点

  • 奇数长度链表(如 1→2→3→4→5)返回中间节点 3

  • 偶数长度链表(如 1→2→3→4)返回第二个中间节点 4

暴力解法

  • 先遍历一遍链表得到长度 n,时间 O(n)

  • 再遍历到第 n/2 个节点,空间 O(1)

  • 总时间 O(n),空间 O(1)

优化解法(快慢指针)

设置两个指针:

  • 慢指针 (slow):每次走 1 步

  • 快指针 (fast):每次走 2 步

当快指针到达链表末尾时,慢指针恰好在中间位置。

循环终止条件证明

  • 奇数长度:快指针到达最后一个节点时,其下一个为空,退出循环,此时慢指针在中间

  • 偶数长度:快指针到达空节点时退出,此时慢指针在第二个中间节点

复杂度

  • 时间:O(n),快指针遍历完整个链表

  • 空间:O(1),仅用两个指针变量

Python 实现

def middleNode(head):
    # 初始化快慢指针均指向头节点
    slow = fast = head
    # 循环终止条件:快指针为空或快指针的下一个为空
    while fast and fast.next:
        slow = slow.next      # 慢指针走一步
        fast = fast.next.next # 快指针走两步
    # 此时 slow 指向中间节点
    return slow

2. 环形链表检测(LeetCode 141)

题目描述

给定一个链表,判断链表中是否有环。要求空间复杂度为 O(1)。

核心思路

  • 快指针每次走 2 步,慢指针每次走 1 步

  • 若链表中有环,快慢指针必然在环内相遇(类似追及问题)

  • 若链表中无环,快指针会先到达链表尾部

相对速度分析

在环中,快指针相对慢指针的相对速度为 1 步 / 循环,因此快指针一定能追上慢指针。

复杂度

  • 时间:O(n),慢指针进入环后,最多遍历环长 n 步就会相遇

  • 空间:O(1)

Python 实现

def hasCycle(head):
    slow = fast = head  # 快慢指针初始化
    while fast and fast.next:
        slow = slow.next        # 慢指针走一步
        fast = fast.next.next   # 快指针走两步
        if slow == fast:        # 两指针相遇,说明有环
            return True
    return False  # 快指针到达末尾,无环

3. 环形链表 II - 找环入口(LeetCode 142)

题目描述

给定一个链表,返回链表中环的入口节点。若链表无环,返回 null。

核心概念

设从链表头到环入口的距离为 A,入口到相遇点的距离为 B,相遇点到入口的距离为 C。

  • 环长 = B + C

  • 慢指针移动距离 = A + B

  • 快指针移动距离 = A + B + k(B + C)(k ≥ 1)

数学推导

由于快指针速度是慢指针的 2 倍:

2(A + B) = A + B + k(B + C)
A + B = k(B + C)
A = (k - 1)B + kC

当 k = 1 时:A = C

即:从相遇点出发走 C 步到达入口,从头节点出发走 A 步也到达入口。

算法步骤

  1. 快慢指针同时出发,快指针速度为慢指针的 2 倍

  2. 两指针相遇后,将其中一个指针重置到头节点

  3. 两指针以相同速度(每次各走一步)继续前进

  4. 再次相遇的位置即为环的入口

为什么慢指针未走完一圈?

最坏情况:慢指针刚进入环时,快指针恰好在慢指针前方一步。此时快指针需要追及的距离为 (B + C - 1),不超过环长。因此慢指针最多走 B + C - 1 < 环长,不会走完一整圈。

复杂度

  • 时间:O(n),第一阶段相遇最多 O(n),第二阶段从入口到相遇最多 O(n)

  • 空间:O(1)

Python 实现

def detectCycle(head):
    slow = fast = head
    # 阶段1:找到快慢指针的相遇点
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            break
    # 无环情况
    if not fast or not fast.next:
        return None
    # 阶段2:找环入口
    # 将 slow 重置到头节点,fast 保持在相遇点
    slow = head
    while slow != fast:
        slow = slow.next
        fast = fast.next
    return slow  # 环入口节点

二、练习题目

1. 234. 回文链表 📐

题目描述

给你一个单链表的头节点 head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false

示例

输入:head = [1,2,2,1]
输出:true

解释:链表正着读和反着读一样,是回文链表。

解题思路

  • 第一步:找到链表的中间节点(快慢指针)

  • 第二步:反转中间节点之后的链表

  • 第三步:双指针比较:头节点到中间 vs 反转后的链表,逐节点比较值是否相等

复杂度

  • 时间:O(n),三次遍历(找中点 O(n/2) + 反转 O(n/2) + 比较 O(n/2))

  • 空间:O(1)

Python 代码

class Solution:
    def middleNode(self, head):
        """快慢指针找中间节点"""
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow

    def reverseList(self, head):
        """反转链表:pre指向已反转部分,cur指向未反转部分"""
        pre, cur = None, head
        while cur:
            nxt = cur.next  # 保存下一个节点
            cur.next = pre  # 反转当前节点的指向
            pre = cur        # pre 前进到当前节点
            cur = nxt        # cur 前进到下一个节点
        return pre

    def isPalindrome(self, head):
        mid = self.middleNode(head)           # 找到中间节点
        head2 = self.reverseList(mid)         # 反转后半部分链表
        while head2:                           # 逐节点比较
            if head.val != head2.val:
                return False
            head = head.next
            head2 = head2.next
        return True

2. 2130. 链表最大孪生和 🎯

题目描述

在一个大小为 nn偶数的链表中,对于 0 <= i <= (n/2) - 1i

  • i 个节点的孪生节点为第 (n - 1 - i) 个节点

  • 孪生和 = 节点值 + 其孪生节点值

给你链表的头节点,返回链表的最大孪生和

示例

输入:head = [5,4,2,1]
输出:6

解释:

  • 节点 0 和节点 3 是孪生节点,孪生和 = 5 + 1 = 6

  • 节点 1 和节点 2 是孪生节点,孪生和 = 4 + 2 = 6

  • 最大孪生和为 6

解题思路

与回文链表类似:

  1. 快慢指针找中点

  2. 反转后半部分链表

  3. 双指针从两端向中间遍历,计算对应节点的孪生和,取最大值

复杂度

  • 时间:O(n)

  • 空间:O(1)

Python 代码

class Solution:
    def middleNode(self, head):
        """快慢指针找中间节点"""
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow

    def reverseList(self, head):
        """反转链表"""
        pre, cur = None, head
        while cur:
            nxt = cur.next
            cur.next = pre
            pre = cur
            cur = nxt
        return pre

    def pairSum(self, head):
        mid = self.middleNode(head)        # 找中点
        head2 = self.reverseList(mid)     # 反转后半部分
        ans = 0
        while head2:                       # 双指针遍历
            ans = max(ans, head.val + head2.val)  # 计算孪生和并取最大
            head = head.next
            head2 = head2.next
        return ans

三、快慢指针解题总结 ✨

核心适用场景

  1. 链表中点查找 - 快指针走完时慢指针在中间

  2. 环形链表检测 - 快慢指针在环内相遇

  3. 环入口定位 - 数学推导证明两指针第二次相遇点即为入口

  4. 链表重排 - 结合找中点 + 反转链表

快慢指针模板

# 基础模板
slow = fast = head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
# 此时 slow 指向中点(奇数)或第二个中点(偶数)

关键数学关系

问题

数学关系

环长

B + C

相遇时慢指针走过的距离

A + B

相遇时快指针走过的距离

A + B + k(B + C)

环入口条件

A = C(当 k = 1 时)

复杂度速记

  • 快慢指针找中点:时间 O(n),空间 O(1)

  • 环形检测:时间 O(n),空间 O(1)

  • 环形入口:时间 O(n),空间 O(1)

  • 结合反转链表:时间 O(n),空间 O(1)

通用技巧

  1. 快指针每次走 2 步,是快慢指针的经典设置

  2. 无环一定终止:快指针先到达尾部

  3. 有环必定相遇:相对速度保证在环内追上

  4. 入口定位重置法:相遇后将任一指针重置到头,再次相遇即入口

评论交流