本文最后更新于32 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com
🔗 排序链表
📌 题目描述
给你链表的头节点 head
,请将其按升序排列并返回排序后的链表。
要求:
- 时间复杂度:O(n log n)
- 空间复杂度:O(log n)(递归栈空间,符合“常数级额外空间”的宽松解释)
- 不能转换成数组排序再重建链表(考察链表原地操作能力)
✅ 示例解析
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
⚠️ 提示
- 链表中节点数目范围:
[0, 5 * 10⁴]
Node.val
范围:[-10⁵, 10⁵]
💡 解法:自顶向下归并排序(Top-Down Merge Sort)—— O(n log n) 时间,O(log n) 空间
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode mid = middleNode(head);
ListNode rightHead = mid.next;
mid.next = null;
ListNode left = sortList(head);
ListNode right = sortList(rightHead);
return merge(left, right);
}
private ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
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),共 log n 层 → 总 O(n log n)
- 治:每层合并总长为 n,共 log n 层 → 总 O(n log n)
- 空间复杂度:O(log n)
- 递归调用栈深度为 log n(链表长度每次减半)
- 符合题目“常数级额外空间”的宽松要求(对比数组归并的 O(n))
⚠️ 注:若要求 严格 O(1) 空间,需使用“自底向上归并排序”,但代码复杂,面试中本解法已足够。
🧩 解题思路
核心思想:分治法(Divide and Conquer) + 归并排序
- 递归终止条件:
head == null
或head.next == null
→ 无需排序,直接返回。
- 分 —— 找中点 & 断链:
- 使用快慢指针找中点(
slow
和fast
)。 fast = head.next
→ 保证偶数节点时,slow
停在左中点(如 4 个节点停在第 2 个)。midNode.next = null
→ 将链表从中间断开,分成head
到midNode
和rightHead
开头的两部分。
- 使用快慢指针找中点(
- 治 —— 递归排序:
left = sortList(head)
→ 递归排序左半部分right = sortList(rightHead)
→ 递归排序右半部分
- 合 —— 合并有序链表:
- 调用
mergeTwoLists(left, right)
,将两个有序链表合并成一个有序链表。 - 使用哨兵节点
dummy
简化头节点处理。
- 调用
- 返回合并结果。