Description
給定一個由
k個已排序鏈結串列組成的陣列,將所有串列合併成一個排序的鏈結串列並回傳。
Example:
Input: lists = [[1,4,5],[1,3,4],[2,6]] Output: [1,1,2,3,4,4,5,6]
Intuition#
核心思路:每次從 k 個串列的頭部選最小值,用 Min Heap 將選擇從 O(k) 降到 O(log k)。
- 暴力法:每次遍歷 k 個頭節點找最小值,共 N 個節點,時間 O(Nk)
- Min Heap:維護 k 個頭節點的最小堆,每次取最小值只要 O(log k)
- Divide and Conquer:兩兩合併,每輪串列數減半,共 log k 輪
Approaches#
1: Divide and Conquer#
- 概念: 類似 Merge Sort 的分治法,每輪將 k 個串列兩兩配對合併,共需 log k 輪
- 時間複雜度:
O(N log k)- N 為總節點數,log k 輪合併 - 空間複雜度:
O(log k)- 遞迴深度
class Solution {
fun mergeKLists(lists: Array<ListNode?>): ListNode? {
if (lists.isEmpty()) return null
return merge(lists, 0, lists.size - 1)
}
private fun merge(lists: Array<ListNode?>, left: Int, right: Int): ListNode? {
if (left == right) return lists[left]
val mid = left + (right - left) / 2
val l1 = merge(lists, left, mid)
val l2 = merge(lists, mid + 1, right)
return mergeTwoLists(l1, l2)
}
private fun mergeTwoLists(l1: ListNode?, l2: ListNode?): ListNode? {
val dummy = ListNode(0)
var tail = dummy
var p1 = l1
var p2 = l2
while (p1 != null && p2 != null) {
if (p1.`val` <= p2.`val`) {
tail.next = p1
p1 = p1.next
} else {
tail.next = p2
p2 = p2.next
}
tail = tail.next!!
}
tail.next = p1 ?: p2
return dummy.next
}
}⭐2: Min Heap (Priority Queue)#
- 概念: 將 k 個串列的頭節點放入最小堆,每次取出最小節點接到結果串列,並將該節點的下一個節點加入堆中
- 時間複雜度:
O(N log k)- 每個節點入堆出堆各一次,每次 O(log k) - 空間複雜度:
O(k)- 堆中最多 k 個節點
class Solution {
fun mergeKLists(lists: Array<ListNode?>): ListNode? {
val pq = PriorityQueue<ListNode>(compareBy { it.`val` })
for (list in lists) {
if (list != null) pq.offer(list)
}
val dummy = ListNode(0)
var tail = dummy
while (pq.isNotEmpty()) {
val node = pq.poll()
tail.next = node
tail = node
if (node.next != null) {
pq.offer(node.next)
}
}
return dummy.next
}
}注意空串列的處理:
lists中可能包含 null 元素,加入 PriorityQueue 前要檢查。
🔑 Takeaways#
- Pattern: 「合併 k 個有序序列」是經典問題,Min Heap 和 Divide and Conquer 都是標準解法
- 關鍵技巧: PriorityQueue 搭配 compareBy 在 Kotlin 中非常簡潔;Divide and Conquer 可複用 mergeTwoLists 子程序