148. Sort List
MediumDescription
給定一個鏈結串列的頭節點
head,將其排序後回傳。要求時間複雜度O(n log n),空間複雜度O(1)。
Example:
Input: head = [4,2,1,3] Output: [1,2,3,4]
Intuition#
核心思路:鏈結串列最適合 Merge Sort,因為合併兩個有序串列是 O(1) 空間的天然操作。
- 陣列的 Merge Sort 需要額外空間來合併,但鏈結串列的合併只需改指針
- 分割:用快慢指針找中點,斷開為兩半
- 合併:與 LC 21 Merge Two Sorted Lists 相同
Approaches#
1: Top-Down Merge Sort (Recursive)#
- 概念: 遞迴地將串列從中點分割,排序後合併
- 時間複雜度:
O(n log n) - 空間複雜度:
O(log n)- 遞迴堆疊深度
class Solution {
fun sortList(head: ListNode?): ListNode? {
if (head?.next == null) return head
// 快慢指針找中點(取前半段的最後一個節點)
var slow = head
var fast = head.next
while (fast?.next != null) {
slow = slow?.next
fast = fast.next?.next
}
// 斷開為兩半
val mid = slow?.next
slow?.next = null
// 遞迴排序
val left = sortList(head)
val right = sortList(mid)
// 合併
return merge(left, right)
}
private fun merge(l1: ListNode?, l2: ListNode?): ListNode? {
val dummy = ListNode(0)
var curr: ListNode = dummy
var p1 = l1
var p2 = l2
while (p1 != null && p2 != null) {
if (p1.`val` <= p2.`val`) {
curr.next = p1
p1 = p1.next
} else {
curr.next = p2
p2 = p2.next
}
curr = curr.next!!
}
curr.next = p1 ?: p2
return dummy.next
}
}⭐2: Bottom-Up Merge Sort (Iterative)#
- 概念: 從大小 1 開始,逐步合併相鄰的子串列,大小依次翻倍,真正的 O(1) 空間
- 時間複雜度:
O(n log n) - 空間複雜度:
O(1)- 純迭代
class Solution {
fun sortList(head: ListNode?): ListNode? {
if (head?.next == null) return head
// 計算長度
var length = 0
var curr = head
while (curr != null) {
length++
curr = curr.next
}
val dummy = ListNode(0)
dummy.next = head
var size = 1
while (size < length) {
var prev: ListNode = dummy
var cur = dummy.next
while (cur != null) {
// 取第一段 (size 個節點)
val left = cur
var i = 1
while (i < size && cur?.next != null) {
cur = cur.next
i++
}
// 取第二段
val right = cur?.next
cur?.next = null // 斷開第一段
cur = right
i = 1
while (i < size && cur?.next != null) {
cur = cur.next
i++
}
// 記住剩餘部分
val remaining = cur?.next
cur?.next = null // 斷開第二段
// 合併兩段
prev.next = merge(left, right)
// prev 移到合併後的尾端
while (prev.next != null) {
prev = prev.next!!
}
cur = remaining
}
size *= 2
}
return dummy.next
}
private fun merge(l1: ListNode?, l2: ListNode?): ListNode? {
val dummy = ListNode(0)
var curr: ListNode = dummy
var p1 = l1
var p2 = l2
while (p1 != null && p2 != null) {
if (p1.`val` <= p2.`val`) {
curr.next = p1
p1 = p1.next
} else {
curr.next = p2
p2 = p2.next
}
curr = curr.next!!
}
curr.next = p1 ?: p2
return dummy.next
}
}找中點時 fast 應從 head.next 開始(而非 head),確保分割的前半段不會多出一個節點,避免無限遞迴。
🔑 Takeaways#
- Pattern: 鏈結串列排序首選 Merge Sort,因為合併有序串列只需 O(1) 空間修改指針
- 關鍵技巧: Top-Down 容易寫但有 O(log n) 遞迴空間;Bottom-Up 是真正 O(1) 空間但實作較複雜