注意归并排序的细节:
用快慢指针找到中间的指针。
中间指针在慢指针后面
注意慢指针要断链

快慢指针原理:
1 | while fast and fast.next: |
退出时有两种可能:
链表长度是偶数
fast先跑到最后一个节点,再走两步直接变成None循环条件里
fast为None,退出
链表长度是奇数
fast最后落在最后一个节点fast.next是None,循环条件里fast.next为None,退出
举例验证
长度 4:
1→2→3→4- 最后一步
fast=4,fast.next=None→ 再走两步时fast=None→ 循环退出
- 最后一步
长度 5:
1→2→3→4→5- 最后一步
fast=5,fast.next=None→ 循环退出
- 最后一步
快慢指针分割链表
1 | mid = slow.next |
也就是说,mid 取的是 slow 的下一个节点。
快慢指针找中点的逻辑
slow每次走一步fast每次走两步循环条件:
while fast and fast.next:
循环退出时,slow 会落在 中点偏左的位置。
为什么不是直接 mid = slow
如果直接取
mid = slow,那么递归时左半部分和右半部分会有 交叉节点。因为
slow还连着后半部分链表,递归时左半部分就不纯粹了。
所以必须 断链:
1 | mid = slow.next |
这样得到:
左半部分:从
head到slow右半部分:从
mid到末尾
举例说明
链表:1 → 2 → 3 → 4
初始:
slow=1, fast=2迭代1:
slow=2, fast=4退出时:
slow=2
这时:
mid = slow.next = 3断链:
slow.next = None
结果:
左半部分:
1 → 2右半部分:
3 → 4
如果错误地用 mid = slow:
左半部分:
1 → 2 → …(还会连到 3)右半部分:
2 → 3 → 4(和左半部分重叠)
递归就会出问题,甚至死循环。
1 | # Definition for singly-linked list. |
排序链表 vs 数组归并排序 的对比总结:
数组 vs 链表归并排序对比
1. 如何分割
数组
通过下标
mid = (left+right)//2来划分区间[left, mid]和[mid+1, right]。不需要真正切断数组,递归时用下标控制。
链表
不能随机访问,只能用 快慢指针 找到中点。
找到
slow后,通过slow.next = None来 断链,分成两段独立子链表。
2. 如何合并
数组
在
merge(arr, left, mid, right)时,需要复制左右两段到临时数组L、R,再一个个写回原数组。时间 O(n),额外空间 O(n)。
链表
用
merge(l1, l2)直接操作指针,把两个有序链表拼接起来。不需要额外数组空间,只需要几个辅助指针。
时间 O(n),空间 O(1)。
3. 递归终止条件
数组
1
if left >= right: return
只剩一个元素(或空区间)时,直接返回。
链表
1
if not head or not head.next: return head
空链表或单节点链表,天然有序,直接返回。
4. 时间复杂度
两者都是:
递归分治深度 O(log n)
每层合并 O(n)
总复杂度 O(n log n)
5. 空间复杂度
数组归并排序
- 需要额外临时数组(每次 merge 时),总体 O(n) 辅助空间。
链表归并排序
只需要少量指针变量,辅助空间 O(1)(如果不算递归栈)。
这是链表排序的一个优势。