Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 13 – Heaps and top-K

Merging k sorted lists with a heap

Merge k sorted lists through a heap that holds one value of each, then find the smallest range that covers every list, starting from the brute force.

  • Intermediate
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 6 of 35 minutes free
Free: Two problems on k sorted lists · The brute force, and why it fails at these sizes · What to notice. With a pass: the other 14 sections and 3 interview answers, about 29 minutes.

What you will learn

  • Merge k sorted lists in O(N log k) time with a heap that holds one entry per list
  • Find the shortest window that covers a value of every list, and prove that the scan never misses it
  • Order ties with the list number, so that the merge is stable and never compares payloads
  • Merge files and other streams lazily with heapq.merge, reading one item ahead in each
  • Compare the heap with scanning, pairwise merging and sorting by counted comparisons

Before you start

What you’ll learn · Before you start

What you will learn

  • Merge k sorted lists in O(N log k) time with a heap that holds one entry per list
  • Find the shortest window that covers a value of every list, and prove that the scan never misses it
  • Order ties with the list number, so that the merge is stable and never compares payloads
  • Merge files and other streams lazily with heapq.merge, reading one item ahead in each
  • Compare the heap with scanning, pairwise merging and sorting by counted comparisons

Before you start

On this page

To merge k sorted lists, keep a min-heap that holds one entry from each list: the smallest value that list has not given out yet. Write out the top, then put the next value of the same list in its place. Each of the N values passes through the heap once, and the heap never holds more than k entries, so the merge costs O(N log k) time and O(k) memory, and it can read files too large for memory one value at a time. The same heap of fronts finds the smallest range that holds a value from every list. Both problems follow, with their brute force.

Two problems on k sorted lists

Problem A: one queue from k counters

A clinic’s k registration counters each log the minute of the day at which they registered each patient, in increasing order. The waiting-room screen needs every registration in one list, by minute; registrations in the same minute go in counter order, lowest first. Return the merged (minute, counter) pairs. Limits: 1 ≤ k ≤ 10,000 counters, N ≤ 1,000,000 registrations, minutes from 0 to 1,439.

The logs [[540, 545, 600], [541, 600], [], [530]] merge to 530@3, 540@0, 541@1, 545@0, 600@0 and 600@1, where 530@3 means minute 530 at counter 3.

Problem B: the shortest window that hears every sensor

Each of k sensors reports its reading times, in seconds, in increasing order. Find the shortest window [a, b], with a and b reading times, that contains a reading of every sensor; among equally short windows, return the earliest. Limits: 1 ≤ k ≤ 1,000 sensors with at least one reading each, N ≤ 200,000 readings, times from 0 to 10⁹.

  • [[3, 11, 19, 27], [8, 14, 25], [1, 17, 30]] gives [14, 19], with 19, 14 and 17 from sensors 0, 1 and 2. [25, 30] is as short but starts later.
  • [[5], [5], [5]] gives [5, 5].

The brute force, and why it fails at these sizes

For problem A, scan the front of every log for the smallest, k looks per registration: 10¹⁰ looks at the limits. For problem B, try every reading as the start a, binary-search each sensor for its first reading at or after a, and stretch the window to the latest: up to N × k = 2 × 10⁸ searches.

Both problems the slow way, with the work counted Python · brute/brute.py
# Both problems the slow way, with the work counted: scanning every log's front for the next registration, and
# trying every reading as the start of a window.
from bisect import bisect_left


def merge_by_scanning(logs):
    """Problem A: look at the front of every log, take the smallest (minute, counter), repeat."""
    fronts = [0] * len(logs)  # fronts[c]: the next unread position of log c
    merged, looks = [], 0
    while True:
        best = None
        for c, log in enumerate(logs):
            looks += 1
            if fronts[c] < len(log) and (best is None or log[fronts[c]] < logs[best][fronts[best]]):
                best = c  # strict <: on a tie the lower counter, seen first, stays
        if best is None:
            return merged, looks  # every log is used up
        merged.append((logs[best][fronts[best]], best))
        fronts[best] += 1


def window_by_trying_starts(sensors):
    """Problem B: every reading as a start; each sensor's first reading at or after it decides the end."""
    best, searches = None, 0
    for start in sorted({t for readings in sensors for t in readings}):
        end = start
        for readings in sensors:
            searches += 1
            i = bisect_left(readings, start)  # the first reading at or after start
            if i == len(readings):
                return best, searches  # nothing from this sensor at or after start, nor after any later start
            end = max(end, readings[i])
        if best is None or end - start < best[1] - best[0]:
            best = (start, end)  # strict <: the earliest start wins a tie
    return best, searches


def show_merge(merged):
    return " ".join(f"{minute}@{c}" for minute, c in merged)


merged, looks = merge_by_scanning([[540, 545, 600], [541, 600], [], [530]])
print("A:", show_merge(merged), f"({looks} looks)")
window, searches = window_by_trying_starts([[3, 11, 19, 27], [8, 14, 25], [1, 17, 30]])
print("B:", list(window), f"({searches} searches)")

# The worst cases at the limits, counted from the loops above rather than run.
k, n = 10_000, 1_000_000
print(f"A at k = {k:,} logs and N = {n:,} entries: k * (N + 1) = {k * (n + 1):,} looks")
k, n = 1_000, 200_000
print(f"B at k = {k:,} sensors and N = {n:,} distinct readings: up to N * k = {n * k:,} binary searches")

Output

A: 530@3 540@0 541@1 545@0 600@0 600@1 (28 looks)
B: [14, 19] (26 searches)
A at k = 10,000 logs and N = 1,000,000 entries: k * (N + 1) = 10,000,010,000 looks
B at k = 1,000 sensors and N = 200,000 distinct readings: up to N * k = 200,000,000 binary searches

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute.py

Sorting the concatenated logs answers problem A in O(N log N) when everything fits in memory, but it needs every entry before it can return the first one.

What to notice

  • In problem A, the next registration is always the smallest of the k unread fronts. The entries behind the fronts never compete.
  • In problem B, a window that hears every sensor is set by k readings, one per sensor, from the smallest of them to the largest.
Four counter logs feeding a min-heap of their fronts, which pops the merged list; three sensors holding 19, 14 and 17, spanning [14, 19].Problem A: four sorted counter logsMin-heap: one front per log(530, 3) on top, then (540, 0), (541, 1)Merged: 530@3, 540@0, 541@1, 545@0, 600@0, 600@1Problem B: one reading held from each sensorWindow [14, 19]: from the smallest held reading to the largestcounter 0: 540, 545, 600counter 1: 541, 600counter 2: no entriescounter 3: 530sensor 0: 3, 11, 19 held, 27sensor 1: 8, 14 held, 25sensor 2: 1, 17 held, 30each log's next entrypop the smallest, push its successorthe same heap of frontsspans

One front per list, for both problems

Text description of the diagram

The upper half shows problem A. Four counter logs are listed: counter 0 holds 540, 545 and 600; counter 1 holds 541 and 600; counter 2 has no entries; counter 3 holds 530. An arrow labelled "each log's next entry" leads to a min-heap that holds one front per log that still has one: (530, 3) on top, then (540, 0) and (541, 1). An arrow labelled "pop the smallest, push its successor" leads to the merged list: 530@3, 540@0, 541@1, 545@0, 600@0 and 600@1, where each entry names its minute and its counter, and the two entries at minute 600 come in counter order.

A dashed arrow labelled "the same heap of fronts" leads to the lower half, which shows problem B. Three sensors are listed with the reading each one holds at one moment: sensor 0 holds 19 of 3, 11, 19 and 27; sensor 1 holds 14 of 8, 14 and 25; sensor 2 holds 17 of 1, 17 and 30. An arrow labelled "spans" leads to the window [14, 19], which runs from the smallest held reading, 14, to the largest, 19, and so contains a reading of every sensor.

Both questions keep one candidate per list and keep asking for the smallest while one list moves on: the cue for a heap from the heaps overview.

Merging k logs, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • The invariant that makes the merge correct, and its cost
  • The shortest covering window: always advance the earliest reading
  • Why the scan never misses the shortest window: a proof
  • A dry run on the three sensors
  • The code in five languages
  • Measured: the heap against scanning, pairwise merging and sorting
  • Merging files and streams lazily with heapq.merge
  • Edge cases and common bugs, each found by a drill
  • Variants: linked lists, the j-th smallest, m of k sensors, common gaps
  • Exercise: the shortest window that covers every list
    • The shortest window that covers every list
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • Why does merging k sorted lists with a heap cost O(N log k) and not O(N log N), and when does the difference matter?
    • A batch job must merge 2,000 sorted log files of about 5 GB each into one sorted file, on a machine with 16 GB of RAM. Describe the merge, how much memory it needs, and what limits its speed.
    • For the smallest range that contains a value from each of k sorted lists, a scan holds one value per list and moves one list forward at each step. Which list must move, why does that never skip the answer, and what changes when values repeat?
    • You must merge k sorted linked lists with N nodes in all. Compare merging them in pairs, round after round, with a heap of the k heads. Which would you write in an interview, and which in a service?
  • Key takeaways

7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 29 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.