LeetCode 148. 排序链表:归并排序的细节解析
·
文章目录
题目描述
LeetCode 148题要求对链表进行排序,时间复杂度需为 O(n log n),且空间复杂度为 O(log n)。由于链表的特殊结构(无法随机访问),归并排序成为实现这一目标的理想选择。本文将详细解析如何通过归并排序解决该问题,并重点探讨一个关键细节:为什么快指针的初始化方式会影响分割结果。
一、方法思路:归并排序的核心步骤
归并排序的核心思想是“分治”:
- 分割链表:将链表分成两个尽可能均匀的子链表。
- 递归排序:对子链表递归排序。
- 合并有序链表:将两个有序子链表合并为一个有序链表。
其中,均匀分割链表是保证时间复杂度的关键。若分割不均,递归深度或每层操作次数可能增加,导致效率下降。
二、关键实现细节:快慢指针分割链表
1. 快慢指针的初始化问题
在分割链表时,我们需要找到中间节点。常见的实现方式是使用快慢指针:
- 慢指针
slow:每次移动一步。 - 快指针
fast:每次移动两步。
但快指针的初始化方式直接影响分割结果。以下是两种初始化方式的对比:
| 初始化方式 | fast = head.next | fast = head |
|---|---|---|
| 链表长度为偶数时 | 均匀分割(左半部分长度=右半部分) | 右半部分可能过短 |
| 链表长度为奇数时 | 均匀分割(左半部分长度=右半部分-1) | 均匀分割 |
2. 为什么选择 fast = head.next?
通过示例分析不同初始化方式的分割结果:
示例1:链表长度为偶数(1->2->3->4)
fast = head.next(初始值fast=2):第1步:slow=2, fast=4 → fast.next=null,停止循环。 分割结果:左半部分`1->2`,右半部分`3->4`(均匀)。fast = head(初始值fast=1):第1步:slow=2, fast=3 → fast.next存在,继续循环。 第2步:slow=3, fast=null → 停止循环。 分割结果:左半部分`1->2->3`,右半部分`4`(不均匀)。
结论:fast = head.next 确保了链表被均匀分割,而 fast = head 在偶数长度时会导致右半部分过短。
三、完整代码实现
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 使用快慢指针找到中间节点(关键:fast初始化为head.next)
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode mid = slow.next;
slow.next = null; // 分割链表为两部分
// 递归排序左右子链表
ListNode left = sortList(head);
ListNode right = sortList(mid);
// 合并有序链表
return merge(left, right);
}
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode curr = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = (l1 != null) ? l1 : l2;
return dummy.next;
}
}
四、复杂度分析
- 时间复杂度:O(n log n)
- 每层递归需要 O(n) 时间合并链表,递归深度为 O(log n)。
- 空间复杂度:O(log n)
- 递归调用栈的深度为 O(log n)。
五、总结
- 均匀分割的重要性:确保递归深度为 O(log n),避免时间复杂度退化。
- 快慢指针的初始化细节:
fast = head.next保证了链表被均匀分割,尤其是在链表长度为偶数时。 - 归并排序的优势:适合链表的顺序访问特性,合并操作无需额外空间(仅需修改指针)。
通过正确实现快慢指针的分割逻辑,归并排序能够高效解决链表的排序问题,满足题目对时间复杂度和空间复杂度的要求。
更多推荐
所有评论(0)