刷題之合併K個排序連結串列
這是崔斯特的第八十四篇原創文章
拼命刷題 (๑• . •๑)
題目:合併 k 個排序連結串列,返回合併後的排序連結串列。
示例:
輸入: [ 1->4->5, 1->3->4, 2->6 ] 輸出: 1->1->2->3->4->4->5->6
思路一
從 21. 合併兩個有序連結串列 的基礎上,我們已經能夠解決兩個有序連結串列的問題,現在是k個有序連結串列,我們可以將第一二個有序連結串列進行合併,然後將新的有序連結串列再繼續跟第三個有序連結串列合併,直到將所有的有序連結串列合併完成。 這樣做思路上是可行的,但是演算法的時間複雜度將會很大,具體就不計算了。有興趣的自己計算下。
思路二
根據思路一,我們是一個一個地將有序連結串列組成新的連結串列,這裡一個進行了k-1次兩個有序連結串列的合併操作。而且隨著新連結串列越來越大,時間複雜度也會越來越高。 這裡有一種簡化的方式,可以先將k個有序連結串列先以2個連結串列為一組進行合併,得出結果後,再將所有的新有序連結串列繼續上面的方式,2個連結串列為一組進行合併。直至將所有的有序連結串列進行合併。 這個思路會比思路一的演算法複雜度少一點。
思路三(推薦)
我們換個不一樣的思路。我們先遍歷一次所有的連結串列中的元素。然後將元素全部放在一個數組裡面。接著對這個陣列進行排序,最終將排序後的數組裡面的所有元素連結起來。 這種方案的複雜度和程式碼量會比前集中思路更好,更簡單。
空間複雜度:因為需要一個數組,所以需要額外的空間。這個空間的大小就是連結串列元素的個數 時間複雜度:假設一個是n個元素,對連結串列進行遍歷(n),對陣列進行排序(排序演算法可以達到nlogn),最終連結所有元素(n),就是 (n+nlogn+n),也就是O(nlogn)。
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def mergeKLists(self, lists): """ :type lists: List[ListNode] :rtype: ListNode """ nodeList = [] for i in range(len(lists)): currentNode = lists[i] while currentNode: nodeList.append(currentNode) currentNode = currentNode.next nodeList = sorted(nodeList, key=lambda x: x.val) tempHead = ListNode(0) currentNode = tempHead for i in range(len(nodeList)): currentNode.next = nodeList[i] currentNode = currentNode.next return tempHead.next
思路四
偷懶方法,使用內建庫 heapq
,最小堆, 每個list有一個指標, k個指標放入堆中, 每次pop出最小的, 然後指向相應list的下一個node, 再push入堆。
最小堆是一個數組, 所有元素滿 heap[k] <= heap[2*k+1]
和 heap[k] <= heap[2*k+2]
, heap[0]即堆頂最小。
# class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: # @param a list of ListNode # @return a ListNode def mergeKLists(self, lists): heap = [] for node in lists: if node != None: heap.append((node.val, node)) heapq.heapify(heap) head = ListNode(0) curr = head while heap: pop = heapq.heappop(heap) curr.next = ListNode(pop[0]) curr = curr.next if pop[1].next: heapq.heappush(heap, (pop[1].next.val, pop[1].next)) return head.next