|
| 1 | +""" |
| 2 | +23. Merge k Sorted Lists |
| 3 | +
|
| 4 | +https://leetcode.com/problems/merge-k-sorted-lists |
| 5 | +
|
| 6 | +NOTES |
| 7 | + * The naive solution to merge k sorted lists is an extension of the solution |
| 8 | + for merging two sorted lists. However, for k lists the time complexity |
| 9 | + becomes non-linear (O(nk)), since every selection costs O(k). To optimize |
| 10 | + the selection process we can use a priority queue, which is implemented as |
| 11 | + a min-heap. A heap offers O(log n) performance for inserts and removals, |
| 12 | + and O(n) to build the heap initially from a set of n elements. |
| 13 | +
|
| 14 | + To start, we build a heap using the head of each list. Next, for each |
| 15 | + selection, we use the find-min operation (O(1)) and delete-min operation |
| 16 | + (O(log k)), removing the minimum value from the heap. Finally, the head of |
| 17 | + the list from which the node was taken is inserted into the heap. The |
| 18 | + selection now has the following time complexity: |
| 19 | +
|
| 20 | + O(k) + O(1) + O(log k) + O(log k) = O(log k) |
| 21 | +
|
| 22 | + This gives an overall time complexity of O(nlog k) and O(k) space |
| 23 | + complexity for the heap. |
| 24 | +
|
| 25 | + Example (where ○ represents null): |
| 26 | +
|
| 27 | + 1 4 5 |
| 28 | + ● → ● → ● → ○ |
| 29 | +
|
| 30 | + 1 3 4 |
| 31 | + ● → ● → ● → ○ |
| 32 | +
|
| 33 | + 2 6 |
| 34 | + ● → ● → ○ |
| 35 | +
|
| 36 | + Build a heap using the head of each list: |
| 37 | +
|
| 38 | + 1 |
| 39 | + ● |
| 40 | + / \ |
| 41 | + 1 2 |
| 42 | + ● ● |
| 43 | +
|
| 44 | + For each selection, remove the minimum value from the heap: |
| 45 | +
|
| 46 | + 4 5 |
| 47 | + ● → ● → ○ |
| 48 | + ↑ |
| 49 | +
|
| 50 | + 1 1 3 4 |
| 51 | + ○ → ● ● → ● → ● → ○ |
| 52 | + ↑ ↑ |
| 53 | +
|
| 54 | + 2 6 |
| 55 | + ● → ● → ○ |
| 56 | + ↑ |
| 57 | +
|
| 58 | + Insert the head of the list from which the node was taken into the heap: |
| 59 | +
|
| 60 | + 1 |
| 61 | + ● |
| 62 | + / \ |
| 63 | + 2 4 |
| 64 | + ● ● |
| 65 | +""" |
| 66 | + |
| 67 | +import heapq |
| 68 | + |
| 69 | +from src.classes import ListNode |
| 70 | + |
| 71 | + |
| 72 | +class HeapNode: |
| 73 | + """ |
| 74 | + Wrapper for a ListNode. |
| 75 | +
|
| 76 | + Enables heap operations by implementing the '<' operation. |
| 77 | + """ |
| 78 | + |
| 79 | + def __init__(self, node: ListNode): |
| 80 | + self.node: ListNode = node |
| 81 | + |
| 82 | + def __lt__(self, other: "HeapNode") -> bool: |
| 83 | + return self.node.val < other.node.val |
| 84 | + |
| 85 | + |
| 86 | +class Solution: |
| 87 | + def mergeKLists(self, lists: list[ListNode | None]) -> ListNode | None: |
| 88 | + if not lists: |
| 89 | + return None |
| 90 | + |
| 91 | + # Build a heap using the head of each list. |
| 92 | + heap: list[HeapNode] = [] |
| 93 | + for head in lists: |
| 94 | + if head: |
| 95 | + heapq.heappush(heap, HeapNode(node=head)) |
| 96 | + |
| 97 | + # Instantiate a head sentinel node. |
| 98 | + prehead = ListNode(-1) |
| 99 | + curr = prehead |
| 100 | + |
| 101 | + # For each selection, use `heappop`, which removes the smallest element |
| 102 | + # from the heap, maintaining the heap invariant. The head of the list |
| 103 | + # from which the node was taken is inserted into the heap. |
| 104 | + while heap: |
| 105 | + _next = heapq.heappop(heap).node |
| 106 | + head = _next.next |
| 107 | + curr.next = _next |
| 108 | + curr = curr.next |
| 109 | + if head: |
| 110 | + heapq.heappush(heap, HeapNode(node=head)) |
| 111 | + |
| 112 | + return prehead.next |
0 commit comments