Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 39

Merging K Sorted Lists

Recommended resources

Hello! Welcome to our next lesson on heaps and priority queues.

In our last session, we saw how a heap of size can efficiently solve "Top K" problems, like finding the K largest numbers in a collection. The core idea was to maintain a small, ordered set of the best candidates. Today, we'll adapt that logic to another high-ROI interview problem: merging K sorted linked lists.

This problem is a classic for a reason: it tests your ability to combine data from multiple sorted sources, a pattern that appears in database systems, log processing, and more. By the end of this 60-minute lesson, you will be able to implement the optimal min-heap solution for this problem, analyze its complexity, and understand why it's superior to other approaches.

1. Understanding the Problem and Initial Thoughts

The problem statement is straightforward: you are given an array containing sorted linked lists, and your task is to merge them into a single, sorted linked list.

For example, given these three lists:

  • 1 -> 4 -> 5
  • 1 -> 3 -> 4
  • 2 -> 6

The merged result should be:
1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6

Before jumping to the optimal solution, it's crucial in an interview to discuss simpler, more intuitive approaches. This demonstrates a methodical problem-solving process. Let's explore two such methods.

L25. Merge K Sorted Lists | Multiple Approaches

The 'take U forward' channel provides an excellent overview of two common but suboptimal approaches. Understanding these will help you articulate the advantages of the heap-based solution in an interview setting.

Watch the video from the beginning to 06:20. The first part (until 06:20) covers a brute-force method. Then, pay attention to the second approach (starting at 06:20) which merges lists two at a time.

Let's summarize the two approaches discussed in the video:

  1. Brute-Force: Collect, Sort, Rebuild

    • Logic: Traverse all lists, collect every node's value into a single large array. Sort this array. Then, build a brand new linked list from the sorted values.
    • Time Complexity: , where is the total number of nodes across all lists. Sorting the elements dominates the runtime.
    • Space Complexity: to store all the node values in the array and for the new list.
    • Verdict: Simple to understand, but inefficient. It fails to leverage the fact that the input lists are already sorted.
  2. Iterative Merging: One by One

    • Logic: Use the "Merge Two Sorted Lists" algorithm repeatedly. Merge list 1 and list 2. Then, merge the result with list 3. Then, merge that result with list 4, and so on, times.
    • Time Complexity: This is more complex to analyze, but it's roughly . The first merge is cheap, but later merges involve traversing increasingly long lists. For example, merging the final list involves traversing almost all nodes again.
    • Space Complexity: if done in-place by rewiring nodes, which is a plus.
    • Verdict: Better than brute-force as it uses the sorted nature of the lists, but still too slow, especially for a large number of lists ().

Both approaches have significant drawbacks. We need a way to find the next smallest node across all lists without re-scanning or re-merging large amounts of data.


2. The Optimal Min-Heap Approach

This is where our knowledge of heaps becomes powerful. At any step, the next node in our final sorted list must be the one with the smallest value among the current heads of the lists.

How can we efficiently find the minimum of elements? A min-heap is the perfect data structure for this job.

The core idea is:

  1. Maintain a min-heap containing just the current head node from each of the lists. The heap's size will therefore be at most .
  2. The node at the top of the min-heap is guaranteed to be the smallest of all the candidates.
  3. We extract this minimum node, add it to our result list, and then insert the next node from its original list into the heap.

This process ensures we are always picking the globally smallest element available and efficiently replacing it with the next candidate from its source list.

Initial State for Merging K Sorted Lists with a Min-Heap
CAPTION: This image illustrates the initial setup. The head nodes of the K sorted lists are inserted into a min-heap. The heap's root (0 in this case) is the first element of our final merged list.

To see this algorithm in action, let's watch a detailed walkthrough.

Merge K Sorted Linked Lists - Leetcode 23 - Heaps (Python)

This video by Greg Hogg provides a concise and clear explanation of the min-heap strategy, complete with a visual walkthrough and a Python implementation. It directly addresses a critical implementation detail you'll need for your code.

Watch from 01:58 to 09:18. This covers the intuition, a step-by-step visual example, and the full Python implementation. Pay special attention to the reason he adds an index i to the tuple being pushed to the heap—it's a crucial trick for handling ties.


3. Implementation and Complexity Analysis

As the video demonstrated, the implementation in Python using the heapq library is elegant. Here is a clean version of the code:

import heapq

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def mergeKLists(lists):
    """
    Merges K sorted linked lists using a min-heap.
    Time: O(N log K), Space: O(K)
    N = total number of nodes, K = number of lists
    """
    # Min-heap will store tuples: (value, list_index, node_object)
    # The list_index is a tie-breaker if values are equal, as Python
    # cannot compare ListNode objects by default.
    min_heap = []
    
    # 1. Initialize the heap with the head of each non-empty list
    for i, head in enumerate(lists):
        if head:
            heapq.heappush(min_heap, (head.val, i, head))
            
    # Dummy node to simplify building the new list
    dummy_head = ListNode(-1)
    current = dummy_head
    
    # 2. Process nodes from the heap until it's empty
    while min_heap:
        # 3. Extract the node with the smallest value
        val, i, node = heapq.heappop(min_heap)
        
        # 4. Append this node to the result list
        current.next = node
        current = current.next
        
        # 5. If the extracted node has a successor, add it to the heap
        if node.next:
            next_node = node.next
            heapq.heappush(min_heap, (next_node.val, i, next_node))
            
    return dummy_head.next
Test your understanding!

In the mergeKLists function, why can't we just push (node.val, node) into the heap? Why is the list_index necessary?

Show answer

If two nodes have the same value (e.g., both are 1), the heap needs a way to break the tie. It will try to compare the next elements in the tuple. If the next element is the ListNode object itself, Python will raise a TypeError because it doesn't know how to determine if one ListNode object is "less than" another.

By adding a unique list_index as the second element of the tuple, (node.val, list_index, node), we provide a guaranteed tie-breaker. If the values are the same, the heap will compare the indices, which are just integers and are always comparable.

Complexity Analysis

Let's formally analyze the performance of this optimal solution.

  • = total number of nodes in all lists.
  • = number of linked lists.

Leetcode 23. Merge k Sorted Lists

The HelloInterview resource provides a concise breakdown of the complexity and summarizes the key learnings from this problem in an interview-friendly way.

Read the 'Complexity Analysis' and 'What We've Learned' sections. This will reinforce your understanding of the performance and the core takeaways.

  • Time Complexity:

    • Initialization: We push the head of each of the lists onto the heap. This takes time.
    • Processing: We perform one heappop and one heappush for each of the total nodes. Since the heap size is always at most , each heap operation costs . This gives a total of .
    • The total time is , which simplifies to because is typically greater than or equal to .
  • Space Complexity:

    • The min-heap stores at most one node from each of the lists at any given time. Therefore, the extra space required is proportional to .

This is a massive improvement over the and approaches, and it's what interviewers expect for this problem.


Conclusion

You have just learned one of the most important applications of heaps in technical interviews. The "k-way merge" pattern is powerful and demonstrates a deep understanding of how to choose the right data structure for the job.

Key Takeaways:

  • Merging K sorted lists can be solved optimally with a min-heap of size .
  • The heap efficiently tracks the smallest element among all list heads, reducing the work per node from (naive scan) to .
  • The overall performance is time and space.
  • Be mindful of implementation details, such as using a tie-breaker in Python's heapq when storing objects.

In our next and final lesson of this module, we will explore another clever heap-based pattern: designing a data structure to find the median from a data stream using two heaps. This will challenge you to think about how two heaps—one min-heap and one max-heap—can work together to partition data and answer statistical queries on the fly.