一、題目
給你一個鏈表數組,每個鏈表都已經按升序排列。
請你將所有鏈表合并到一個升序鏈表中,返回合并后的鏈表。
二、解題思路
- 創建一個最小堆(優先隊列)來存儲所有鏈表的頭節點。這樣我們可以始終取出當前所有鏈表中值最小的節點。
- 初始化一個虛擬頭節點(dummy),它將作為合并后鏈表的頭節點。
- 創建一個指針(current)指向虛擬頭節點,用于構建新的鏈表。
- 循環執行以下步驟直到所有鏈表都被合并: a. 從最小堆中取出最小節點。 b. 將current指針移動到這個最小節點。 c. 將最小節點的next指針指向最小堆中的下一個最小節點,或者如果當前鏈表已經沒有更多節點,則指向null。 d. 將取出的節點的原鏈表的下一個節點加入到最小堆中,以便后續處理。
- 當所有鏈表都合并完畢,current指針將指向合并后鏈表的尾節點。返回虛擬頭節點的下一個節點即為合并后的鏈表。
三、具體代碼
import java.util.PriorityQueue;class Solution {public ListNode mergeKLists(ListNode[] lists) {// 創建一個最小堆來存儲鏈表頭節點PriorityQueue<ListNode> minHeap = new PriorityQueue<>((a, b) -> a.val - b.val);// 初始化虛擬頭節點ListNode dummy = new ListNode(0);ListNode current = dummy;// 初始化最小堆for (ListNode list : lists) {if (list != null) {minHeap.offer(list);}}// 開始合并鏈表while (!minHeap.isEmpty()) {// 取出最小節點ListNode minNode = minHeap.poll();// 將current指向這個最小節點current.next = minNode;// 移動current指針current = current.next;// 如果還有下一個節點,加入到最小堆中if (minNode.next != null) {minHeap.offer(minNode.next);}}// 返回合并后的鏈表頭節點return dummy.next;}
}// ListNode的定義已經在問題描述中給出
四、時間復雜度和空間復雜度
1. 時間復雜度
-
時間復雜度是O(NlogK)。
-
初始化最小堆的時間復雜度是O(N),其中N是所有鏈表中元素的總數。這是因為我們需要遍歷每個鏈表的頭節點,并將它們加入到最小堆中。對于每個節點,最小堆的插入操作是O(logK),K是鏈表的數量。但是,由于每個節點最多被插入一次,所以這部分的總時間復雜度是O(N)。
-
合并鏈表的循環中,我們從最小堆中取出節點,這個操作是O(logK)。然后,我們將取出的節點的下一個節點(如果有的話)再次加入到最小堆中,這個操作同樣是O(logK)。這個循環會執行N次,因為我們需要處理所有N個元素。所以,這部分的總時間復雜度是O(NlogK)。
2. 空間復雜度
-
空間復雜度是O(K)。
-
最小堆存儲了所有鏈表的頭節點,最多時有K個節點,所以空間復雜度是O(K)。
-
合并后的鏈表不需要額外的空間,因為它是直接在原有鏈表的基礎上構建的。
-
虛擬頭節點dummy是一個常數級別的空間開銷。
請注意,這里的K是鏈表的數量,而不是鏈表的長度。在實際應用中,如果鏈表數量遠小于鏈表長度,那么K可能會比N小得多,這樣時間復雜度和空間復雜度都會相對較低。
五、總結知識點
-
鏈表(Linked List):代碼中使用了單向鏈表的數據結構,每個
ListNode
包含一個值(val
)和一個指向下一個節點的指針(next
)。 -
優先隊列(PriorityQueue):為了有效地合并多個已排序的鏈表,代碼使用了Java的
PriorityQueue
,它是一個基于優先級堆(通常是最小堆)的無界隊列。在這個場景中,它被用來存儲所有鏈表的頭節點,以便快速取出當前最小的節點。 -
比較器(Comparator):在創建
PriorityQueue
時,使用了自定義的比較器來定義節點之間的排序規則。這里通過比較節點的值(val
)來實現升序排列。 -
虛擬頭節點(Dummy Node):為了避免在合并鏈表時處理特殊情況(如空鏈表),代碼中創建了一個虛擬頭節點
dummy
。這樣可以簡化代碼邏輯,因為始終有一個有效的current
節點可以連接新的節點。 -
循環和條件判斷:在合并鏈表的循環中,使用了
while
循環和if
條件判斷來控制流程,確保在每次迭代中都取出最小節點,并將其正確地連接到合并后的鏈表中。 -
指針操作:在鏈表操作中,
current
指針被用來遍歷和構建新的鏈表。每次循環中,current
都會移動到新添加的節點,以便為下一個節點的添加做準備。 -
返回值:最終,函數返回虛擬頭節點的下一個節點,即合并后鏈表的實際頭節點。這是鏈表操作中常見的技巧,用于簡化邊界條件的處理。
以上就是解決這個問題的詳細步驟,希望能夠為各位提供啟發和幫助。