Appearance
链表的中间节点
更新: 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 恰好停在中点。
核心思路
slow 和 fast 都从 head 出发,循环条件为 fast && fast.next:
slow = slow.next(走一步);fast = fast.next.next(走两步);- 当
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 | 一趟完成,慢指针天然落中点 |
边界与易错点
常见踩坑
- 循环条件必须是
fast && fast.next:fast每次跳两步,必须同时保证fast和fast.next都非空,否则fast.next.next会抛空指针错误。顺序也不能反(要先判fast再判fast.next)。 - 返回第一个还是第二个中间节点:题目要求偶数时返回第二个。用
while (fast && fast.next)+ 两者都从head出发,正好返回第二个;若想返回第一个,需改用while (fast.next && fast.next.next)。 - 单节点 / 空链表:
head为单节点时循环不进入,直接返回head(它就是中点);head为null时也直接返回null。快慢写法天然覆盖。 - 别忘了两者同起点:
slow和fast都从head出发。若起点不一致,中点位置会偏移。 - 不要用
slow.val返回:题目要返回节点,不是值,返回slow本身。
简易解题思路(记忆口诀)
一句话记忆:「快慢同起点,快跑两慢跑一,快到头慢到腰」
把它想象成两个人在同一起跑线赛跑:
- 快慢同起点:
slow和fast都从头节点出发; - 快跑两、慢跑一:快的人一步迈两格,慢的人一步迈一格;
- 快到头、慢到腰:当快的人冲到终点(走完全程 )时,慢的人只走了一半 ,正好站在链表的"腰"(中间)。
核心画面:快指针路程是慢指针的两倍 → 快指针撞墙时,慢指针必在正中间。 偶数长度时快指针"跨过头"落到 null,慢指针顺势多站一格,自然落在第二个中间节点——这恰好是题目要的答案,一个条件全搞定。