← 返回题目列表

链表排序为什么常用归并排序?快慢指针如何拆分链表?

高频 中等 第 8 / 23 题 更新于 2026/08/03
分治链表归并排序

简化版

链表排序常用归并排序,因为链表不适合随机访问,但很适合拆分和合并。

用快慢指针找到链表中点,把链表断成两半,递归排序左右链表。

最后用合并两个有序链表的方式合并,时间复杂度 O(n log n),额外空间主要是递归栈。

详细版

数组排序常用快排,但链表上快排 partition 需要频繁改指针,不如归并自然。

链表归并排序流程:

  1. 如果链表为空或只有一个节点,直接返回;
  2. 用快慢指针找到中点;
  3. 从中点断开链表;
  4. 递归排序左右两段;
  5. 合并两个有序链表。

合并阶段只需要改 next 指针,不需要额外复制大量元素。

完整版教学

一、为什么链表不适合普通数组排序思路

链表不能通过下标 O(1) 访问中间元素,很多依赖随机访问的排序技巧会变得别扭。比如堆排序需要频繁访问父子下标,快排分区也需要大量交换节点或值。归并排序只需要顺序遍历和改指针,天然贴合链表结构。

记忆钩子:数组怕搬运,链表怕随机访问;链表排序优先想归并。

排序思路数组适配性链表适配性
快速排序随机访问和交换方便分区改指针较麻烦
堆排序下标访问父子节点方便随机访问差
归并排序需要辅助数组顺序合并很自然

二、快慢指针如何找中点

快指针一次走 2 步,慢指针一次走 1 步。当快指针到尾部时,慢指针接近中点。为了断链,通常还需要记录慢指针前一个节点 prev

1 -> 4 -> 2 -> 3
slow 最终到 2 附近
prev.next = null 断成两段

断链非常重要,否则递归时左右链表仍然连在一起,会造成无限递归。

三、分治递归结构

链表排序的分治结构和归并排序一致:

sort(head)
├─ sort(left half)
└─ sort(right half)
merge(left, right)

每次拆成两半,问题规模减半;合并时线性扫描两个已排序链表。递归深度约为 log n

四、代码模板

实现如下:

function sortList(head) {
  if (!head || !head.next) return head

  let slow = head
  let fast = head
  let prev = null
  while (fast && fast.next) {
    prev = slow
    slow = slow.next
    fast = fast.next.next
  }
  prev.next = null

  const left = sortList(head)
  const right = sortList(slow)
  return merge(left, right)
}

function merge(a, b) {
  const dummy = { next: null }
  let cur = dummy
  while (a && b) {
    if (a.val <= b.val) {
      cur.next = a
      a = a.next
    } else {
      cur.next = b
      b = b.next
    }
    cur = cur.next
  }
  cur.next = a || b
  return dummy.next
}

相等时优先接左链表节点,可以保持稳定性。

五、复杂度分析

每一层合并所有链表段,总共会访问 n 个节点。递归层数是 log n,所以时间复杂度是:

O(n log n)

自顶向下递归需要 O(log n) 调用栈。若用自底向上归并,可以把额外栈空间降到 O(1),但实现更复杂。

六、常见误区与追问

  • 误区:找到中点后忘记断链。 左右链表仍连接会导致递归无法缩小问题。
  • 误区:合并时创建新节点。 可以直接复用原节点,避免额外内存和数据复制。
  • 误区:认为链表归并一定 O(n) 空间。 辅助数组不需要,递归栈是 O(log n);自底向上可做到常数额外空间。
  • 追问:为什么不使用快排? 链表随机访问差,快排分区和交换不如数组自然,且稳定性也不好。
  • 追问:如何处理偶数长度链表中点? 只要能拆成两个更短链表即可,左右差 1 不影响复杂度。

这些问题考的是链表结构和排序算法的匹配度。

七、加强记忆

链表排序记成“快慢找中点,断链分两半,归并接指针”。分治负责把大链表拆小,合并负责把两个有序链表线性拼回去。链表不擅长随机访问,所以归并比很多数组排序更自然。写代码时最重要的坑是断链和复用节点。