Taro Logo

Merge k Sorted Lists

Hard
Amazon logo
Amazon
1 view
Topics:
Linked ListsGreedy AlgorithmsArrays

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.

Merge all the linked-lists into one sorted linked-list and return it.

Examples:

  1. Input: lists = [[1,4,5],[1,3,4],[2,6]] Output: [1,1,2,3,4,4,5,6] Explanation: The linked-lists are:
    [
      1->4->5,
      1->3->4,
      2->6
    ]
    
    merging them into one sorted list:
    1->1->2->3->4->4->5->6
    
  2. Input: lists = [] Output: []
  3. Input: lists = [[]] Output: []

Constraints:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i] is sorted in ascending order.
  • The sum of lists[i].length will not exceed 10^4.

Describe an algorithm to merge the k sorted lists into a single sorted list. Analyze the time and space complexity of your solution. What are some edge cases to consider?

Solution


Naive Approach: Brute Force

One straightforward approach is to collect all the values from the linked lists into a single array, sort the array, and then create a new linked list from the sorted array.

Algorithm:

  1. Iterate through each linked list in the input lists.
  2. Extract all the values from each linked list and store them in a single array.
  3. Sort the array in ascending order.
  4. Create a new linked list from the sorted array.

Code (Python):

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def mergeKLists(lists):
    values = []
    for linked_list in lists:
        current = linked_list
        while current:
            values.append(current.val)
            current = current.next
    
    values.sort()
    
    if not values:
        return None
    
    head = ListNode(values[0])
    current = head
    for i in range(1, len(values)):
        current.next = ListNode(values[i])
        current = current.next
        
    return head

Time Complexity: O(N log N), where N is the total number of nodes in all linked lists. This is due to the sorting step.

Space Complexity: O(N), where N is the total number of nodes. This is because we store all node values in an array.

Optimal Approach: Priority Queue (Min-Heap)

A more efficient approach is to use a priority queue (min-heap) to keep track of the smallest node from all the linked lists. This allows us to merge the lists in sorted order without explicitly sorting all the values at once.

Algorithm:

  1. Create a min-heap and insert the head nodes of all non-empty linked lists into the heap. The heap should be ordered by the node values.
  2. Create a dummy head for the merged linked list.
  3. While the heap is not empty:
    • Extract the node with the smallest value from the heap.
    • Append this node to the merged linked list.
    • If the extracted node has a next node, insert the next node into the heap.
  4. Return the next of the dummy head, which is the head of the merged linked list.

Code (Python):

import heapq

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def mergeKLists(lists):
    heap = []
    for node in lists:
        if node:
            heapq.heappush(heap, (node.val, node))
    
    dummy = ListNode()
    current = dummy
    
    while heap:
        val, node = heapq.heappop(heap)
        current.next = node
        current = current.next
        
        if node.next:
            heapq.heappush(heap, (node.next.val, node.next))
            
    return dummy.next

Time Complexity: O(N log k), where N is the total number of nodes in all linked lists and k is the number of linked lists. Each insertion and extraction from the heap takes O(log k) time, and we perform these operations for each of the N nodes.

Space Complexity: O(k), where k is the number of linked lists. This is the space used by the priority queue.

Edge Cases:

  • Empty input list: If the input lists is empty, return None.
  • Empty linked lists: If any of the linked lists are empty, they should be skipped when initializing the priority queue. This is handled in the code by checking if node: before pushing onto the heap.
  • All empty linked lists: If all linked lists are empty, the priority queue will be empty from the start, and the algorithm will correctly return an empty linked list.