Problem overview
The method mergeKLists receives lists, a Python list of k linked lists. Each linked list is sorted in ascending order and is given by its head node, or None if it is empty. The method returns the head of one linked list that holds all the values in sorted order.
For [1 → 4 → 5, 1 → 3 → 4, 2 → 6] the merged list is 1 → 1 → 2 → 3 → 4 → 4 → 5 → 6. With no lists at all, or with a single empty list, the answer is an empty list, which is None.
The statement asks for O(n log k) time and O(k) extra space, where n is the total number of nodes.
Approach: always take the smallest of the k heads
The smallest remaining value overall is always at the front of one of the lists. A simple method scans all k heads every time to find it. That costs O(k) per output node, O(n · k) in total.
A min-heap does the same job faster. It keeps the smallest item at the top and can insert and remove in O(log k). Put the head of every non-empty list in the heap. Repeatedly remove the smallest node and add it to the result. Then add that node's successor, if any, to the heap, so its list stays represented. When the heap is empty, every list has been consumed.
The heap never holds more than one node per list, so its size is at most k. Each of the n output nodes costs one removal and at most one insertion, each O(log k), which gives O(n log k).
Step by step
- Create the heap.
minNodesis a plain Python list. Theheapqfunctions treat a list as a heap. - Store tuples, not nodes.
heapqorders items with<, andListNodedoesn't define it. Comparing two nodes raisesTypeError. So each entry is(val, counter, node), which compares byvalfirst. - Add a tiebreaker counter.
counter = 0. Tuples compare element by element, and when twovals are equal the comparison moves on to the second element. Because every counter value is different, it decides before the node is ever compared. - Loop over the heads with
for head in lists. - Push the non-empty ones. If
headisn'tNone, push(head.val, counter, head)and add 1 tocounter. - Create a dummy node with
ListNode(). The merged list will hang offdummy.next, which avoids a special case for the first node. - Keep a
tailpointer starting at the dummy, always the last node appended. - Loop while
minNodesis non-empty. An empty list is false in Python, sowhile minNodes:is the whole test. - Pop the smallest entry and unpack it into
val,_andminNode. The underscore marks the counter as unused. - Append a new node holding
valto the result:tail.next = ListNode(val). - Feed the heap. If
minNode.nextexists, push(minNode.next.val, counter, minNode.next)and increasecounter. - Advance
tailto the node just added. - Return
dummy.next, the head of the merged list.
The solution in Python
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
minNodes = []
counter = 0
for head in lists:
if head:
heapq.heappush(minNodes, (head.val, counter, head))
counter += 1
dummy = ListNode()
tail = dummy
while minNodes:
val, _, minNode = heapq.heappop(minNodes)
tail.next = ListNode(val)
if minNode.next:
heapq.heappush(minNodes, (minNode.next.val, counter, minNode.next))
counter += 1
tail = tail.next
return dummy.next
Python details that matter:
heapqis a min-heap on a list.heappushandheappopkeepminNodes[0]the smallest entry. The list itself isn't sorted, only arranged so that the smallest is first. Use these functions rather than reading or sorting the list yourself. The module comes fromimport heapq.- Tuple comparison.
(1, 0, a) < (1, 1, b)is decided by the second elements, soaandbare never compared. Without the counter, two lists whose heads share a value would crash withTypeError: '<' not supported between instances of 'ListNode' and 'ListNode'. - Truthiness of nodes.
if head:is false only forNone, because a node object is true unless the class defines otherwise. A node holding the value 0 is still true; the test looks at the node, not atval. - New nodes. The result is built from fresh
ListNode(val)objects instead of relinking the originals. The input lists stay untouched, at the cost of memory for the output.
Complexity
Time: O(n log k). Building the heap takes at most k pushes, each O(log k). The main loop runs n times with one pop and at most one push, each O(log k).
Space: O(k) extra, for the heap of at most k entries. The merged list holds n new nodes, but that is the output, not working storage.
Edge cases and common mistakes
- No lists.
[]leaves the heap empty, the loop never runs, anddummy.nextisNone. - Only empty lists.
[None]givesNonein the same way. - Equal values across lists. Two heads with the same value work because of the counter. This is the most common crash in heap solutions.
- Pushing bare nodes or
(val, node)pairs. Both fail on ties with aTypeError. - Pushing a node that is
None. Without theif head:andif minNode.next:checks,None.valraisesAttributeError. - Returning
dummy. The dummy node is a placeholder. The answer starts atdummy.next. - Skipping the successor push. Without it, each list contributes only its first node.
Testing
[1 → 4 → 5, 1 → 3 → 4, 2 → 6]gives1 → 1 → 2 → 3 → 4 → 4 → 5 → 6.[]gives an empty list.[None]gives an empty list.[1 → 1, 1]gives1 → 1 → 1, which exercises ties.