Search lands in PR-5.1 (Pagefind).

How-to Advanced

Chapter 1 Updated

Merge K Sorted Lists

Min-heap of heads; pop smallest, push its successor, repeat.

  • Full 8m
  • Revision
  • Flow

Problem Statement

You are given k individually sorted sequences (linked lists or arrays). Merge them into a single sorted sequence and return it.

Input

A list of sorted sequences.

Example input
lists = [
  [1, 4, 5],
  [1, 3, 4],
  [2, 6]
]

Output

The fully merged sorted sequence.

Expected output
[1, 1, 2, 3, 4, 4, 5, 6]

Constraints

  • 0 ≤ k ≤ 10⁴
  • 0 ≤ lists[i].length, total elements ≤ 10⁴
  • Values are comparable (typically integers).

Intuition

At any moment, the smallest unvisited element across all lists is the head of one of the lists. Push all current heads into a min-heap. Pop the smallest, append to output, push its successor from the same list. Repeat until the heap is empty. The heap holds at most k entries at a time, giving O(log k) per operation.

Contrast: merging pairwise is O(n · k); divide-and-conquer merging is also O(n log k) but uses more memory. The heap approach is the cleanest to implement.

Walkthrough — lists=[[1,4,5],[1,3,4],[2,6]]
seed heap with heads: (1, lst0, 0), (1, lst1, 0), (2, lst2, 0)
 
pop (1, lst0, 0) → emit 1, push (4, lst0, 1)
pop (1, lst1, 0) → emit 1, push (3, lst1, 1)
pop (2, lst2, 0) → emit 2, push (6, lst2, 1)
pop (3, lst1, 1) → emit 3, push (4, lst1, 2)
pop (4, lst0, 1) → emit 4, push (5, lst0, 2)
pop (4, lst1, 2) → emit 4, lst1 exhausted
pop (5, lst0, 2) → emit 5, lst0 exhausted
pop (6, lst2, 1) → emit 6, lst2 exhausted
 
result = [1, 1, 2, 3, 4, 4, 5, 6] ✓

Solution

Approach A — Array-of-arrays

merge_k_sorted_arrays.py
import heapq
 
def mergeKSortedArrays(lists: list[list[int]]) -> list[int]:
    # seed heap with first element of each list: (value, list_idx, pos_in_list)
    heap = [(lst[0], i, 0) for i, lst in enumerate(lists) if lst]
    heapq.heapify(heap)
 
    result: list[int] = []
    while heap:
        val, li, pi = heapq.heappop(heap)
        result.append(val)
        if pi + 1 < len(lists[li]):
            heapq.heappush(heap, (lists[li][pi + 1], li, pi + 1))
    return result

Approach B — Linked-list (LeetCode #23)

merge_k_lists.py
import heapq
 
class ListNode:
    def __init__(self, val=0, nxt=None):
        self.val, self.next = val, nxt
 
def mergeKLists(lists: list[ListNode | None]) -> ListNode | None:
    heap: list[tuple[int, int, ListNode]] = []
    for i, node in enumerate(lists):
        if node:
            heapq.heappush(heap, (node.val, i, node))   # i is tie-breaker
 
    dummy = ListNode(0)
    tail = dummy
    while heap:
        val, i, node = heapq.heappop(heap)
        tail.next = node
        tail = node
        if node.next:
            heapq.heappush(heap, (node.next.val, i, node.next))
    return dummy.next

Complexity

MetricValueReason
TimeO(N log k)Let N = total elements across all lists. Each is pushed and popped once; heap size is capped at k, so each op is O(log k).
SpaceO(k)Heap holds at most one entry per list. Output is O(N) — required, not auxiliary.

Edge Case Thinking

ScenarioHandling
Empty lists in inputSkip them when seeding. Otherwise you’d push None or crash on index 0.
All lists emptyHeap never seeds; return empty result.
k == 1Degenerates to returning the single list unchanged.
Highly unequal list lengthsCost is still N log k; one very long list doesn’t hurt the bound.
Duplicate values across listsTie-breaker guarantees deterministic output and avoids comparator crashes.

Comments

Comments are disabled in this environment. Set PUBLIC_GISCUS_REPO, PUBLIC_GISCUS_REPO_ID, and PUBLIC_GISCUS_CATEGORY_ID to enable.