题目描述

✅ 143. 重排链表

image-20260928183428966

image-20260928183428967

题意分析

将链表按原来的第一个、最后一个、第二个、倒数第二个的顺序交替连接,直到所有节点都被使用。必须原地调整节点的 next,不能只交换节点值;函数不返回新链表,因为重排后的第一个节点仍是原头节点。

需要的节点来自两端:前半部分按原顺序读取,后半部分按反向顺序读取。单链表只能向后走,如果每次重新寻找尾节点会反复遍历,因此先把链表拆成两段,再反转后半段,就能把两端交替取节点转成两个指针向前移动。

解法:找中点 + 反转后半段 + 交替合并

核心思路

[!blue]

先将链表拆成前后两段,让前半段长度不少于后半段。使用从头出发的快慢指针,slow 每次走一步,fast 每次走两步;只有 fast 后面还存在两个节点时才继续。长度为偶数时,slow 停在左侧中点;长度为奇数时,停在唯一中点,中间节点归前半段。

保存 second = slow.next 后,将 slow.next 设为空,形成两条独立链表。必须先断开旧连接:接下来的反转和合并都把两段当作互不重叠的链表,若前半段仍能沿旧指针进入后半段,最后交叉连接时就可能重新接回已处理节点而形成环。

接着反转后半段,使原尾节点成为这一段的新头。反转时,pre 指向已反转部分的头,cur 指向下一个待处理节点;先保存原后继,再令 cur.next = pre,最后推进两个指针。这样后半段就能按原链表从尾到中间的顺序依次读取。

最后用 first、second 分别指向两段当前尚未合并的节点,每轮把 second 插入 first 后面。先保存两边的后继 firstNext、secondNext,再执行 first.next = second、second.next = firstNext,随后分别沿保存的后继前进。保存后继的原因是两条当前连接都要改写,改写后再读取就不再是原来的剩余部分。

每轮恰好取前半段一个节点和后半段一个节点,顺序正好对应从原链表两端向内交替。前半段至少一样长,因此只要 second 非空,first 就一定存在。后半段耗尽时,偶数长度的链表已经全部接好;奇数长度时,前半段只多出一个中间节点,它已经自然接在最后,且断链时已将其后继置空。

解题步骤

  1. 空链表或只有一个节点时直接结束。
  2. 快慢指针同时从头出发,用 fast.next 和 fast.next.next 都非空作为继续条件,定位前半段尾节点 slow。
  3. 保存后半段头节点 second = slow.next,再断开 slow.next。
  4. 用迭代反转后半段,并让 second 指向反转后的头节点。
  5. 从原头节点和 second 开始交替连接;每轮先保存两边后继,再修改连接,最后推进两个指针。
  6. 后半段为空时结束,重排已经反映在原链表的节点连接中。

代码实现

class Solution {
    public void reorderList(ListNode head) {
        if (head == null || head.next == null) {
            return;
        }

        ListNode slow = head;
        ListNode fast = head;

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

        ListNode second = slow.next;

        // 先断开两段的旧连接,后续交替合并才不会接回成环。
        slow.next = null;
        second = reverse(second);
        merge(head, second);
    }

    private ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;

        while (cur != null) {
            // 反转前保存后继,避免丢失未处理部分。
            ListNode next = cur.next;

            cur.next = pre;
            pre = cur;
            cur = next;
        }

        return pre;
    }

    private void merge(ListNode first, ListNode second) {
        // 前半段长度不少于后半段,第二段耗尽就完成交替连接。
        while (second != null) {
            // 两段的后继都要先保存,再进行交叉连接。
            ListNode firstNext = first.next;
            ListNode secondNext = second.next;

            first.next = second;
            second.next = firstNext;
            first = firstNext;
            second = secondNext;
        }
    }
}
func reorderList(head *ListNode) {
    if head == nil || head.Next == nil {
        return
    }

    slow := head
    fast := head
    for fast.Next != nil && fast.Next.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
    }

    second := slow.Next
    // 先断开两段的旧连接,后续交替合并才不会接回成环。
    slow.Next = nil
    second = reverseList(second)
    mergeList(head, second)
}

func reverseList(head *ListNode) *ListNode {
    var pre *ListNode
    cur := head

    for cur != nil {
        // 反转前保存后继,避免丢失未处理部分。
        next := cur.Next
        cur.Next = pre
        pre = cur
        cur = next
    }

    return pre
}

func mergeList(first *ListNode, second *ListNode) {
    // 前半段长度不少于后半段,第二段耗尽就完成交替连接。
    for second != nil {
        // 两段的后继都要先保存,再进行交叉连接。
        firstNext := first.Next
        secondNext := second.Next
        first.Next = second
        second.Next = firstNext
        first = firstNext
        second = secondNext
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,找中点、反转后半段和交替合并都只做线性次数的指针操作,几个阶段相加仍为线性时间。
  • 空间复杂度:$O(1)$,只使用固定数量的指针,辅助函数均为迭代实现,没有递归栈。

关键点总结

[!green]

  • 找中点后必须断链,否则合并时可能形成环。
  • 修改 next 前先保存后继,避免丢失剩余链表。
  • 合并循环以后半段是否为空为条件,可统一处理奇偶长度。

易错点总结

[!yellow]

  • 忘记执行 slow.next = null,会保留旧连接并可能形成环。
  • 改指针前没有保存后继,会丢失尚未处理的节点。
  • 快慢指针的循环边界写错,可能使后半段比前半段长。
  • 空链表未提前返回会访问空指针;单节点用其余流程也能正确处理,入口提前返回只是省去无需进行的重排。

相似题目

题目 难度 关联与区别
876. 链表的中间结点 简单 快慢指针找中点是重排的第一步,之后才能拆成两半。
206. 反转链表 简单 反转后半段使尾部节点变得可顺序访问,再与前半段交替连接。
234. 回文链表 简单 拆分链表后反转并重新连接;本题从中点拆分并交替合并首尾,该题反转后半段后比较对称值。
92. 反转链表 II 中等 拆分链表后反转并重新连接;本题从中点拆分并交替合并首尾,该题只反转指定区间。
25. K 个一组翻转链表 困难 拆分链表后反转并重新连接;本题从中点拆分并交替合并首尾,该题按固定长度分组反转。
补充题 108. 交替合并两个链表 简单 都交替接入两条链的节点;本题先找到中点并反转后半段,补充题直接给出两条链。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/43660029
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!