[LeetCode解題攻略] 23. Merge k Sorted Lists

更新於 發佈於 閱讀時間約 13 分鐘

題目描述

給定 k 個遞增排序的鏈表,將它們合併成一個遞增排序的鏈表,並返回合併後的鏈表。

範例 1:

Input: lists = [[1,4,5],[1,3,4],[2,6]]
Output: [1,1,2,3,4,4,5,6]

範例 2:

Input: lists = []
Output: []

範例 3:

Input: lists = [[]]
Output: []

解題思路

k 個排序鏈表合併成一個排序鏈表是經典的合併問題,可以考慮以下幾種解法:

  1. 暴力解法:將所有節點收集到一個列表中,排序後再組裝成一個新的鏈表。
  2. 兩兩合併:反覆將兩個鏈表合併,直到所有鏈表合併完成。
  3. 分治法:類似歸併排序,將 k 個鏈表分成兩半,遞歸合併。
  4. 優先隊列/最小堆:利用最小堆(優先隊列)高效地合併節點。

解法一:暴力解法

思路

  1. 遍歷所有鏈表節點,將它們的值存入一個列表中。
  2. 對列表進行排序。
  3. 根據排序後的結果構建新的鏈表。

程式碼

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
nodes = []

# 收集所有節點的值
for l in lists:
while l:
nodes.append(l.val)
l = l.next

# 排序節點
nodes.sort()

# 根據排序結果重新構建鏈表
dummy = ListNode()
current = dummy
for val in nodes:
current.next = ListNode(val)
current = current.next

return dummy.next

時間與空間複雜度

  • 時間複雜度:收集節點的值需要 O(N)(其中 N 是所有鏈表節點的總數),排序需要 O(N log N),總時間複雜度為 O(N log N)。
  • 空間複雜度:O(N),使用了額外的列表來存儲節點值。

解法二:兩兩合併

思路

  1. 使用合併兩個排序鏈表的方式,依次合併鏈表。
  2. 反覆合併,直到所有鏈表合併成一個。

程式碼

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
if not lists:
return None

while len(lists) > 1:
merged_lists = []

# Merge two lists at a time
for i in range(0, len(lists), 2):
l1 = lists[i]
l2 = lists[i + 1] if i + 1 < len(lists) else None
merged_lists.append(self.mergeTwoLists(l1, l2))

lists = merged_lists

return lists[0]

def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode()
current = dummy

while l1 and l2:
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next

current.next = l1 if l1 else l2
return dummy.next

時間與空間複雜度

  • 時間複雜度
    • 每次合併需要 O(N),總共需要合併 log k 次,總時間複雜度為 O(N log k),其中 N 是總節點數,k 是鏈表數量。
  • 空間複雜度
    • 使用了遞迴合併兩個鏈表,但遞迴深度固定,額外空間為 O(1)。

解法三:分治法

思路

  1. 使用分治法,將鏈表分為左右兩半。
  2. 遞歸地合併兩部分。
  3. 最後返回合併後的結果。

程式碼

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
if not lists:
return None
if len(lists) == 1:
return lists[0]

mid = len(lists) // 2
left = self.mergeKLists(lists[:mid])
right = self.mergeKLists(lists[mid:])
return self.mergeTwoLists(left, right)

def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode()
current = dummy

while l1 and l2:
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next

current.next = l1 if l1 else l2
return dummy.next

時間與空間複雜度

  • 時間複雜度
    • 與兩兩合併法相同,為 O(N log k)。
  • 空間複雜度
    • 使用了遞迴分治,遞迴深度為 O(log k),空間複雜度為 O(log k)。

解法四:最小堆法(優先隊列)

思路

  1. 使用優先隊列(最小堆)存儲每個鏈表的當前節點。
  2. 每次取出最小的節點,並將其後繼節點加入堆中。
  3. 重複上述步驟,直到所有節點被處理完。

程式碼

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
class HeapNode:
def __init__(self, node):
self.node = node

def __lt__(self, other):
return self.node.val < other.node.val

heap = []

# 初始化堆
for l in lists:
if l:
heapq.heappush(heap, HeapNode(l))

dummy = ListNode()
current = dummy

while heap:
# 取出最小節點
smallest = heapq.heappop(heap).node
current.next = smallest
current = current.next

# 如果還有後繼節點,加入堆中
if smallest.next:
heapq.heappush(heap, HeapNode(smallest.next))

return dummy.next

時間與空間複雜度

  • 時間複雜度
    • 每次操作堆需要 O(log k),處理所有節點需要 O(N log k),總時間複雜度為 O(N log k)。
  • 空間複雜度
    • 優先隊列存儲了最多 k 個節點,空間複雜度為 O(k)。

總結

對於 k 值較小時,分治法和兩兩合併法都表現不錯;當 k 值較大時,優先隊列法因其高效性表現最佳。

歡迎來到我的部落格!這裡記錄了軟體工程師的日常生活點滴,並分享程式設計與演算法的實用教學。無論你是初學者還是有經驗的開發者,都能在這裡找到深入淺出的技術解析與實戰技巧。此外,我也會分享工作中的心路歷程與學習心得,讓你不僅學到技術,更能瞭解軟體開發的實際應用與挑戰。希望透過這個平台,能與你共同成長,激發對技術的熱情!
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
給定一個整數 n,請生成所有可能由 n 對括號組成的 有效括號組合。
給定兩個遞增排序的鏈表 list1 和 list2,將它們合併為一個新的 遞增排序 的鏈表,並返回這個新鏈表。新的鏈表應該由這兩個鏈表中的節點組成,而不應創建新的節點。最後回傳合併後的鍊錶的頭節點。
給定一個僅包含字元 '(', ')', '{', '}', '[' 和 ']' 的字串 s,確定輸入字串是否有效。
給定一個Linked list的head,從這個Linked list的末尾刪除第 n 個節點並返回其head。
給定一個包含 n 個整數的陣列 nums,傳回所有唯一的四元組 [nums[a], nums[b], nums[c], nums[d]] 的數組,使得該答案滿足題目的每項要求。
給定一組字符串,找出它們的最長公共前綴 (Longest Common Prefix)。如果沒有公共前綴,返回空字串 ""。
給定一個整數 n,請生成所有可能由 n 對括號組成的 有效括號組合。
給定兩個遞增排序的鏈表 list1 和 list2,將它們合併為一個新的 遞增排序 的鏈表,並返回這個新鏈表。新的鏈表應該由這兩個鏈表中的節點組成,而不應創建新的節點。最後回傳合併後的鍊錶的頭節點。
給定一個僅包含字元 '(', ')', '{', '}', '[' 和 ']' 的字串 s,確定輸入字串是否有效。
給定一個Linked list的head,從這個Linked list的末尾刪除第 n 個節點並返回其head。
給定一個包含 n 個整數的陣列 nums,傳回所有唯一的四元組 [nums[a], nums[b], nums[c], nums[d]] 的數組,使得該答案滿足題目的每項要求。
給定一組字符串,找出它們的最長公共前綴 (Longest Common Prefix)。如果沒有公共前綴,返回空字串 ""。
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
題目敘述 Sort Array by Increasing Frequency Leetcode #1636 給定一個輸入陣列,請依照出現頻率的多寡從低頻到高頻排列陣列元素。 如果有兩個元素的出現頻率相同,依照元素大小從大到小排列。 測試範例 Example 1: Input: nums
Thumbnail
題目敘述 Merge Nodes in Between Zeros 給定一個鏈結串列,合併非零區間的節點(以加總的方式合併),輸出合併後的鏈結串列。
Thumbnail
Append Characters to String to Make Subsequence 給定兩個字串s和字串t。 請計算最少的字元串接數量是多少,串接在s的尾端,使得t是s的子序列。 測試範例 Example 1: Input: s = "coaching", t =
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定我們兩個字串。 第一個是指定順序的字串order。 第二個是輸入字串s。 要求我們依據order給定的順序,重新排列s。 如果出現order中沒有出現的字母,任意位置皆可。 合法答案可能不只一組,輸出其中一種即可。 題目的原文敘述 測試範例 Example
Thumbnail
題目敘述 題目會給定一個鏈結串列 Linked List的頭部結點,要求我們根據索引的奇偶數重新排列。奇數索引的在前,偶數索引的在後。數的時候,從Head節點的索引=1開始數。 例如: 1 -> 2 -> 3 -> 4 -> 5 重新排列為 1 -> 3 -> 5 -> 2 -> 4
Thumbnail
題目敘述 題目會給定我們兩個輸入字串word1, word2,要求我們依照word1,word2,word1,word2, ... 交叉前進的方式,合併兩個字串,作為輸出。 題目的原文敘述 測試範例 Example 1: Input: word1 = "abc", word2 = "pq
Thumbnail
題目敘述 題目會給我們一個輸入陣列nums,和一個指定的k值。 請問,在輸入陣列nums中,有幾個子陣列的元素總合恰好為k ? 例如: nums = [1,2,3], k = 3 則有兩個子陣列的元素總合為3,分別是[1,2] 和 [3] 如果是第一次聽到或接觸前綴和prefix的同學
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
題目敘述 Sort Array by Increasing Frequency Leetcode #1636 給定一個輸入陣列,請依照出現頻率的多寡從低頻到高頻排列陣列元素。 如果有兩個元素的出現頻率相同,依照元素大小從大到小排列。 測試範例 Example 1: Input: nums
Thumbnail
題目敘述 Merge Nodes in Between Zeros 給定一個鏈結串列,合併非零區間的節點(以加總的方式合併),輸出合併後的鏈結串列。
Thumbnail
Append Characters to String to Make Subsequence 給定兩個字串s和字串t。 請計算最少的字元串接數量是多少,串接在s的尾端,使得t是s的子序列。 測試範例 Example 1: Input: s = "coaching", t =
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定我們兩個字串。 第一個是指定順序的字串order。 第二個是輸入字串s。 要求我們依據order給定的順序,重新排列s。 如果出現order中沒有出現的字母,任意位置皆可。 合法答案可能不只一組,輸出其中一種即可。 題目的原文敘述 測試範例 Example
Thumbnail
題目敘述 題目會給定一個鏈結串列 Linked List的頭部結點,要求我們根據索引的奇偶數重新排列。奇數索引的在前,偶數索引的在後。數的時候,從Head節點的索引=1開始數。 例如: 1 -> 2 -> 3 -> 4 -> 5 重新排列為 1 -> 3 -> 5 -> 2 -> 4
Thumbnail
題目敘述 題目會給定我們兩個輸入字串word1, word2,要求我們依照word1,word2,word1,word2, ... 交叉前進的方式,合併兩個字串,作為輸出。 題目的原文敘述 測試範例 Example 1: Input: word1 = "abc", word2 = "pq
Thumbnail
題目敘述 題目會給我們一個輸入陣列nums,和一個指定的k值。 請問,在輸入陣列nums中,有幾個子陣列的元素總合恰好為k ? 例如: nums = [1,2,3], k = 3 則有兩個子陣列的元素總合為3,分別是[1,2] 和 [3] 如果是第一次聽到或接觸前綴和prefix的同學