Leetcode-148.排序链表
注意归并排序的细节:
用快慢指针找到中间的指针。
中间指针在慢指针后面
注意慢指针要断链
快慢指针原理:
while fast and fast.next:slow = slow.nextfast = fast.next.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
→ 循环退出
- 最后一步
快慢指针分割链表
mid = slow.next
slow.next = None
也就是说,mid
取的是 slow 的下一个节点。
快慢指针找中点的逻辑
-
slow
每次走一步 -
fast
每次走两步 -
循环条件:
while fast and fast.next:
循环退出时,slow
会落在 中点偏左的位置。
为什么不是直接 mid = slow
-
如果直接取
mid = slow
,那么递归时左半部分和右半部分会有 交叉节点。 -
因为
slow
还连着后半部分链表,递归时左半部分就不纯粹了。
所以必须 断链:
mid = slow.next
slow.next = None # 把 slow 和 mid 之间切断
这样得到:
-
左半部分:从
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
(和左半部分重叠)
递归就会出问题,甚至死循环。
# Definition for singly-linked list.# class ListNode:# def __init__(self, val=0, next=None):# self.val = val# self.next = nextclass Solution:def merge(self,L1,L2):dummy=ListNode(0)tail=dummywhile L1 and L2 :if L1.val > L2.val:tail.next=L2L2=L2.nextelse:tail.next=L1L1=L1.nexttail=tail.nextwhile L1:tail.next=L1L1=L1.nexttail=tail.nextwhile L2:tail.next=L2L2=L2.nexttail=tail.nextreturn dummy.nextdef merge_sort(self,head):if not head or not head.next:return headslow,fast=head,head.nextwhile fast and fast.next :slow=slow.nextfast=fast.next.nextmid=slow.nextslow.next=Noneleft=self.merge_sort(head)right=self.merge_sort(mid)return self.merge(left,right)def sortList(self, head):return self.merge_sort(head)
排序链表 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. 递归终止条件
-
数组
if left >= right: return
只剩一个元素(或空区间)时,直接返回。
-
链表
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)(如果不算递归栈)。
-
这是链表排序的一个优势。
-