Skip to content

链表的中间节点

更新: 7/5/2026 字数: 0 字 时长: 0 分钟

题目描述

给你单链表的头节点 head,请你找出并返回链表的中间节点

如果有两个中间节点(链表长度为偶数),则返回第二个中间节点。

输入:head = [1,2,3,4,5]
输出:节点 3        // 长度 5,唯一中间节点

输入:head = [1,2,3,4,5,6]
输出:节点 4        // 长度 6,两个中间节点取第二个(4)

"中间"在链表里并不好找——链表不像数组能用下标 arr[len/2] 直接定位,你必须先知道长度才能算出中点位置。而求长度本身就要遍历一趟。有没有办法一趟就搞定?

寻找链表中间节点的快慢双指针方法

思路拆解

暴力解法:两趟遍历(先数长度)

最直觉的想法:先遍历一趟数出总长 ,中间节点是第 个(0 基索引);再遍历一趟走到它。

点击展开两趟遍历解法(可行,但需两趟)
js
function middleNode(head) {
  // 第一趟:数长度
  let len = 0;
  for (let node = head; node; node = node.next) len++;

  // 第二趟:走到第 ⌊len/2⌋ 个节点(0 基)
  let node = head;
  for (let i = 0; i < Math.floor(len / 2); i++) node = node.next;

  return node;
}

两趟遍历有什么不足?

它正确、好懂,时间也是 ,但要遍历两趟。而且在数据流场景(只能顺序读一次、无法回头)下,第一趟数完长度后你可能已经无法从头再来。面试常追问:"能否只用一趟?"——这就引出了快慢指针。

最优解法:快慢指针(一趟遍历)

核心洞察:让 slow 一次走一步、fast 一次走两步。fast 走到链表末尾时,它走过的路程是 slow 的两倍,所以 slow 恰好停在中点。

核心思路

slowfast 都从 head 出发,循环条件为 fast && fast.next:

  1. slow = slow.next(走一步);
  2. fast = fast.next.next(走两步);
  3. fast 无法再走两步(到达末尾或末尾前一个)时停止,此时 slow 就在中点。

为什么 slow 落在中点? 设链表长 。任意时刻 fast 走过的步数是 slow 的两倍。当 fast 到达终点(走了约 步)时,slow 走了约 步:

而循环条件 fast && fast.next 天然处理了奇偶:偶数长度时会返回第二个中间节点,正好符合题目要求。

奇偶长度的差异

循环条件如何自动适配奇偶

  • 奇数(如 5):fast 最终停在最后一个节点(fast.next === null),slow 停在唯一中点(第 3 个);
  • 偶数(如 6):fast 最终停在 null(fast === null),slow 停在第二个中间节点(第 4 个)。

同一个 while (fast && fast.next) 条件把两种情况都覆盖了,无需任何特判——这正是它的优雅之处。

奇数与偶数长度链表的中间节点区别

交互式动画演示

下面用样例 head = [1,2,3,4,5] 演示快慢指针一趟解法的完整过程。点击 下一步 观察 slow 走一步、fast 走两步,直到 fast 到达末尾、slow 停在中点:

步骤 1 / 4初始化
slow (慢·1步)fast (快·2步)中间节点
fastslow
1
2
3
4
5
null
初始化:slow 与 fast 都从头节点 1 出发。slow 每次走 1 步,fast 每次走 2 步。

完整代码(JavaScript)

高亮行为快慢指针的核心:慢走一步、快走两步。

js
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
function middleNode(head) {
  let slow = head, fast = head;

  // fast 每次走两步,需保证 fast 和 fast.next 都存在
  while (fast && fast.next) {
    slow = slow.next;        // 慢指针走一步
    fast = fast.next.next;   // 快指针走两步
  }

  return slow; // fast 到末尾时,slow 恰好在中点
}
变体:若偶数长度想返回「第一个」中间节点
js
// 把循环条件改成 fast.next && fast.next.next
function middleNode(head) {
  let slow = head, fast = head;
  while (fast.next && fast.next.next) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow; // 偶数长度时返回第一个中间节点(如 6 个返回第 3 个)
}

复杂度分析

设链表长度为

解法时间复杂度空间复杂度遍历趟数说明
两趟遍历2先数长度,再走到中点
数组辅助1全部入数组,取 arr[⌊L/2⌋]
快慢指针1一趟完成,慢指针天然落中点

边界与易错点

常见踩坑

  1. 循环条件必须是 fast && fast.next:fast 每次跳两步,必须同时保证 fastfast.next 都非空,否则 fast.next.next 会抛空指针错误。顺序也不能反(要先判 fast 再判 fast.next)。
  2. 返回第一个还是第二个中间节点:题目要求偶数时返回第二个。用 while (fast && fast.next) + 两者都从 head 出发,正好返回第二个;若想返回第一个,需改用 while (fast.next && fast.next.next)
  3. 单节点 / 空链表:head 为单节点时循环不进入,直接返回 head(它就是中点);headnull 时也直接返回 null。快慢写法天然覆盖。
  4. 别忘了两者同起点:slowfast 都从 head 出发。若起点不一致,中点位置会偏移。
  5. 不要用 slow.val 返回:题目要返回节点,不是值,返回 slow 本身。

简易解题思路(记忆口诀)

一句话记忆:「快慢同起点,快跑两慢跑一,快到头慢到腰」

把它想象成两个人在同一起跑线赛跑:

  • 快慢同起点:slowfast 都从头节点出发;
  • 快跑两、慢跑一:快的人一步迈两格,慢的人一步迈一格;
  • 快到头、慢到腰:当快的人冲到终点(走完全程 )时,慢的人只走了一半 ,正好站在链表的"腰"(中间)。

核心画面:快指针路程是慢指针的两倍 → 快指针撞墙时,慢指针必在正中间。 偶数长度时快指针"跨过头"落到 null,慢指针顺势多站一格,自然落在第二个中间节点——这恰好是题目要的答案,一个条件全搞定。