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

Frontier heaps for k-th smallest pairs

The k pairs with the smallest sums from two sorted lists, without pricing the whole grid of pairs: the problem, the brute force and the signal for a heap.

  • Advanced
  • 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: 7 of 35 minutes free
Free: The problem: the k cheapest combos on a canteen menu · The brute force, and why it fails at these sizes · What to notice. With a pass: the other 13 sections and 3 interview answers, about 28 minutes.

What you will learn

  • Find the k smallest pair sums of two sorted lists in O(k log k) time with a frontier heap
  • Explore a sorted grid with a heap that holds only the edge of what has been taken
  • Prevent a grid search from offering the same pair twice, and compare two ways of doing it
  • Prove that the frontier never skips the next smallest pair
  • Choose a different method when only the k-th sum is needed and k is huge

Before you start

What you’ll learn · Before you start

What you will learn

  • Find the k smallest pair sums of two sorted lists in O(k log k) time with a frontier heap
  • Explore a sorted grid with a heap that holds only the edge of what has been taken
  • Prevent a grid search from offering the same pair twice, and compare two ways of doing it
  • Prove that the frontier never skips the next smallest pair
  • Choose a different method when only the k-th sum is needed and k is huge

Before you start

On this page

To find the k pairs with the smallest sums from two sorted lists, picture the sums as a grid: row i, column j holds a[i] + b[j], and the grid rises along every row and down every column. The smallest pair sits in the corner, and every later one lies next to a pair already taken, so a heap that holds only that edge, the frontier, hands out the pairs in order with O(k log k) work instead of pricing all n × m pairs. The same frontier finds the k-th smallest value of any matrix whose rows and columns are sorted. The problem, its brute force and the signal follow.

The problem: the k cheapest combos on a canteen menu

The problem

A canteen lists its n mains and its m sides, each sorted by price, cheapest first. A combo is one main and one side, (i, j), and costs a[i] + b[j]. Return the k cheapest combos, cheapest first; combos with the same price come in order of i, then of j. Limits: 1 ≤ n, m ≤ 100,000; 1 ≤ k ≤ 10,000, and when k exceeds n × m, every combo is returned; prices from 1 to 10⁹.

  • Mains [100, 130, 200], sides [20, 50, 90] and k = 5 give (0, 0) 120, (0, 1) 150, (1, 0) 150, (1, 1) 180 and (0, 2) 190. The two combos at 150 come in order of the main.
  • Mains [5], sides [1, 1] and k = 3 give (0, 0) and (0, 1): only two combos exist.

The brute force, and why it fails at these sizes

Price every combo, sort them all and keep the first k. It is correct, but at the limits the grid has n × m = 10¹⁰ cells: 80 GB just to hold their prices as 8-byte numbers, then a sort of 10¹⁰ items to keep 10,000 of them. A heap of size k over all the combos saves the memory, yet it still prices every one of the 10¹⁰.

Every combo priced and sorted, with the work at the limits Python · brute/brute.py
# The brute force: price every (main, side) combo, sort them all and keep the first k. Correct, and it shows what
# the answer must look like, but the work is the full grid of combos.


def k_cheapest_brute(mains, sides, k):
    combos = [(m + s, i, j) for i, m in enumerate(mains) for j, s in enumerate(sides)]
    combos.sort()  # by price, then by the main's position, then by the side's
    return combos[:k], len(combos)


for mains, sides, k in (([100, 130, 200], [20, 50, 90], 5), ([5], [1, 1], 3)):
    best, priced = k_cheapest_brute(mains, sides, k)
    print(f"k = {k}:", ", ".join(f"({i}, {j}) {price}" for price, i, j in best), f"[{priced} combos priced]")

n = m = 100_000
print(f"at the limits: {n:,} mains x {m:,} sides = {n * m:,} combos to price and sort, to keep k = 10,000")
print(f"the prices alone, as 8-byte numbers: {n * m * 8 / 10**9:,.0f} GB")

Output

k = 5: (0, 0) 120, (0, 1) 150, (1, 0) 150, (1, 1) 180, (0, 2) 190 [9 combos priced]
k = 3: (0, 0) 6, (0, 1) 6 [2 combos priced]
at the limits: 100,000 mains x 100,000 sides = 10,000,000,000 combos to price and sort, to keep k = 10,000
the prices alone, as 8-byte numbers: 80 GB

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

What to notice

A 3 by 3 grid of combo prices: the three cheapest taken, the three cells next to them marked candidate, the rest not reached.Combo prices: main i (rows) + side j (columns)Rows and columns rise, so every candidatesits next to a taken combo.The next cheapest is the candidate at 180.(0, 0) 120taken(1, 0) 150taken(2, 0) 220candidate(0, 1) 150taken(1, 1) 180candidate(2, 1) 250not reached(0, 2) 190candidate(1, 2) 220not reached(2, 2) 290not reached

The price grid after the three cheapest combos

Text description of the diagram

The grid shows the price of every combo of the canteen example: main i down the rows, with prices 100, 130 and 200, and side j across the columns, with prices 20, 50 and 90. Each cell holds its position (i, j) and its price, the sum of the two.

Row 0 holds (0, 0) 120, (0, 1) 150 and (0, 2) 190. Row 1 holds (1, 0) 150, (1, 1) 180 and (1, 2) 220. Row 2 holds (2, 0) 220, (2, 1) 250 and (2, 2) 290. Prices rise along every row and down every column.

The three cheapest combos, (0, 0) at 120, (0, 1) at 150 and (1, 0) at 150, are marked taken and drawn with a double border. The three cells next to them, (0, 2) at 190, (1, 1) at 180 and (2, 0) at 220, are marked candidate. The other three, (1, 2), (2, 1) and (2, 2), are marked not reached and drawn dashed.

Below the grid, a note says that because rows and columns rise, every candidate is next to a taken combo, and the next cheapest combo is the candidate at 180.

  • The grid is sorted twice. Prices rise along every row, because the sides are sorted, and down every column, because the mains are.
  • So a combo can be the next cheapest only once the combo to its left and the one above it are taken. The candidates form a thin edge around the taken corner, never the whole grid.

Keeping that edge in a min-heap and taking its cheapest combo again and again is the pattern of this lesson. After three combos, the edge in the example holds three candidates, and the cheapest of them, 180, comes next.

The frontier, step by step: rows started on demand Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Two other frontiers, and the seen set one of them needs
  • Why the next cheapest combo is always in the heap: the invariant and a proof
  • A dry run on the canteen menu
  • Measured: three frontiers on 100,000 mains and 100,000 sides
  • When only the k-th price is needed: binary search on the price
  • The code in five languages
  • Edge cases and common bugs, each found by a drill
  • Variants: sorted matrices, three lists and the dearest combos
  • Exercise: the k cheapest combos of two sorted menus
    • The k cheapest combos of two sorted menus
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • Why does the grid of sums a[i] + b[j] of two sorted lists rise along every row and down every column, and what does that tell you about where the next smallest sum can be?
    • A k-smallest-pairs solution starts at (0, 0) and pushes both neighbours, (i + 1, j) and (i, j + 1), of every pair it pops. On some inputs it returns the same pair twice. Explain why, and give two fixes with their costs.
    • Two sorted lists hold 100,000 values each, from 1 to 10⁹. You need the k-th smallest pair sum for k up to 10¹⁰, and nothing else. Which method do you choose, what does it cost, and how do you know the value it returns is a real pair sum?
    • Mains, sides and drinks are each sorted by price, with up to 100,000 items in each list. Return the k cheapest meals of one item from each, for k up to 10,000. Extend the frontier, and compare it with any simpler method you can find.
  • Key takeaways

6 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 25 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.