leetcode
解题思路
对链表进行升序排序的最佳方法是 归并排序(Merge Sort),其时间复杂度为 O(n log n),空间复杂度为 O(log n)(递归调用栈深度)。核心步骤如下:
- 分割链表:使用快慢指针找到链表中点,将链表分为左右两部分。
- 递归排序:对左右子链表分别递归排序。
- 合并有序链表:合并两个已排序的链表,生成最终有序链表。
代码实现
// 定义链表节点结构体 type ListNode struct {Val intNext *ListNode }// 主函数:归并排序入口 func sortList(head *ListNode) *ListNode {// 递归终止条件:空链表或单节点if head == nil || head.Next == nil {return head}// Step 1: 找到链表中点并分割mid := getMid(head)left := headright := mid.Nextmid.Next = nil // 断开左右链表// Step 2: 递归排序左右子链表left = sortList(left)right = sortList(right)// Step 3: 合并有序链表return merge(left, right) }// 快慢指针找中点(慢指针停在中间左侧) func getMid(head *ListNode) *ListNode {slow, fast := head, head.Nextfor fast != nil && fast.Next != nil {slow = slow.Nextfast = fast.Next.Next}return slow }// 合并两个有序链表 func merge(l1, l2 *ListNode) *ListNode {dummy := &ListNode{} // 虚拟头节点简化操作tail := dummy// 比较节点值,按升序连接for l1 != nil && l2 != nil {if l1.Val < l2.Val {tail.Next = l1l1 = l1.Next} else {tail.Next = l2l2 = l2.Next}tail = tail.Next}// 处理剩余节点if l1 != nil {tail.Next = l1} else {tail.Next = l2}return dummy.Next }
代码解析
-
结构体定义
ListNode
结构体包含Val
(节点值)和Next
(指向下一节点的指针),是链表的标准表示。 -
快慢指针分割链表
- 快指针
fast
每次移动两步,慢指针slow
每次移动一步。当fast
到达链表末尾时,slow
位于中间节点的左侧(偶数长度链表)或正中间(奇数长度链表)。 - 通过
mid.Next = nil
断开链表,确保左右子链表独立排序。
- 快指针
-
递归排序与合并
- 递归终止条件处理空链表和单节点情况,直接返回无需操作。
merge
函数利用虚拟头节点dummy
简化合并逻辑,逐个比较节点值并按升序连接。
复杂度分析
指标 | 值 | 说明 |
---|---|---|
时间复杂度 | O(n log n) | 分治策略,每层递归处理 n 个节点 |
空间复杂度 | O(log n) | 递归调用栈深度(平衡树高度) |
示例验证
func main() {// 示例1:输入 [4,2,1,3] → 输出 [1,2,3,4]head1 := &ListNode{4, &ListNode{2, &ListNode{1, &ListNode{3, nil}}}}sorted1 := sortList(head1)printList(sorted1) // 输出:1 → 2 → 3 → 4// 示例2:输入 [-1,5,3,4,0] → 输出 [-1,0,3,4,5]head2 := &ListNode{-1, &ListNode{5, &ListNode{3, &ListNode{4, &ListNode{0, nil}}}}}sorted2 := sortList(head2)printList(sorted2) // 输出:-1 → 0 → 3 → 4 → 5// 示例3:输入 [] → 输出 []var head3 *ListNodesorted3 := sortList(head3)printList(sorted3) // 输出:空 }// 辅助函数:打印链表 func printList(head *ListNode) {for head != nil {fmt.Printf("%d → ", head.Val)head = head.Next}fmt.Println("nil") }
关键点与边界处理
-
快慢指针初始值
fast
初始化为head.Next
,确保偶数长度链表分割时slow
停在左半部分末尾(如示例1的分割点为节点2)。 -
合并剩余节点
merge
函数末尾直接链接剩余链表,避免遗漏未处理节点。 -
空链表处理
主函数优先判断head == nil
,直接返回空链表。
与其他方法对比
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
归并排序 | 时间/空间最优,稳定性高 | 递归可能栈溢出 | 大规模链表排序 |
插入排序 | 代码简单,无需额外空间 | 时间复杂度 O(n²) | 小规模或基本有序链表 |
快速排序 | 平均时间 O(n log n) | 最坏情况 O(n²) | 内存敏感场景 |
总结
通过归并排序的分治策略,该实现高效解决了链表排序问题。