← 返回题目列表

如何重排链表为 L0→Ln→L1→Ln-1 的形式?

高频 中等 第 13 / 28 题 更新于 2026/07/29
链表重排链表快慢指针反转链表

简化版

重排链表常用三步:快慢指针找到中点,把后半段反转,再把前半段和反转后的后半段交替合并。整个过程时间 O(n),额外空间 O(1)。

详细版

例如:

1 -> 2 -> 3 -> 4 -> 5
重排后:
1 -> 5 -> 2 -> 4 -> 3

做法是先找到中点 3,把后半段 4 -> 5 反转成 5 -> 4,再和前半段 1 -> 2 -> 3 交替连接。面试要强调:不是交换节点值,而是调整节点指针;合并前要断开前后两段,避免形成环。

完整版教学

一、重排链表本质是首尾交替

题目要求把链表:

L0 -> L1 -> L2 -> ... -> Ln

变成:

L0 -> Ln -> L1 -> Ln-1 -> L2 -> Ln-2 ...

链表不能像数组那样 O(1) 访问尾部,所以不能每次从尾巴取节点。更好的办法是把后半段整体反转,让尾部节点变成容易顺序访问的链表。

记忆钩子:先找中点,再反后半,最后拉拉链。

二、第一步用快慢指针找中点

快指针一次走两步,慢指针一次走一步。快指针到尾部时,慢指针在中间。

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

对于 1->2->3->4->5,slow 最后在 3。对于 1->2->3->4,slow 最后在 2。

这能保证前半段长度大于或等于后半段,交替合并时更方便。

三、第二步反转后半段

找到中点后,把后半段切下来:

ListNode second = slow.next;
slow.next = null;

然后反转 second:

before:
1 -> 2 -> 3    4 -> 5

after reverse:
1 -> 2 -> 3    5 -> 4

断开 slow.next 很重要。如果不断开,后面交替合并时可能形成环或保留旧连接。

四、第三步交替合并两段链表

现在有两条链表:

first:  1 -> 2 -> 3
second: 5 -> 4

合并时每次从 second 取一个插到 first 后面:

while (second != null) {
  ListNode next1 = first.next;
  ListNode next2 = second.next;

  first.next = second;
  second.next = next1;

  first = next1;
  second = next2;
}

过程:

1 -> 5 -> 2 -> 4 -> 3

因为前半段长度不小于后半段,循环条件用 second != null 就够了。

五、为什么不能简单交换值

有些题目允许改节点值,但链表题通常希望你调整指针。交换值会有几个问题:

问题说明
节点可能存复杂对象复制成本高或语义不对
外部可能引用节点节点身份不应被值替换
题目明确要求重排节点交换值不符合要求

面试里优先按“改指针”回答,除非题目明确允许交换值。

六、复杂度和边界

找中点 O(n),反转后半段 O(n),合并 O(n),总时间仍是 O(n)。只用常数个指针,额外空间 O(1)。

边界:

空链表 -> 不处理
1 个节点 -> 不处理
2 个节点 -> 不处理或自然保持
奇数长度 -> 中点留在最后
偶数长度 -> 两段一样长

代码开始可以写:

if (head == null || head.next == null) return;

七、常见误区与追问

  • 误区:重排链表就是交换节点值。 高频面试语义通常是调整节点指针,不改值。
  • 误区:找中点后不用断开。 不断开前后段,交替合并可能形成环。
  • 误区:后半段不反转也能顺序取尾节点。 单链表从尾部取节点不方便,会导致 O(n²)。
  • 追问:为什么用快慢指针? 单次遍历找到中点,避免先统计长度再走一遍。
  • 追问:为什么合并循环看 second? 前半段长度不小于后半段,second 用完就完成重排。
  • 追问:复杂度是多少? 时间 O(n),额外空间 O(1)。

八、加强记忆

重排链表记成“三段式操作”:快慢指针找中点,切开并反转后半段,最后像拉链一样交替合并。关键边界是要断开中点后的旧连接,合并时保存 next1next2,避免丢链。它考的是链表指针组织能力,不是数组式首尾随机访问。