Skip to content
Reliable Data Engineering
Practice problem medium heapgreedycompaction
Practise with timer, notes and rubric

Cheapest Way to Merge Sorted Files

Pattern: Heap (Top-K and Greedy Merging) · Difficulty: Medium · Asked at: Databricks, Snowflake, Confluent

Classic version: LeetCode 1167

Problem

A compaction job must merge n sorted files into one. Merging two files of sizes a and b costs a + b (every byte is read and written once) and produces a file of size a + b. Files can be merged in any order. Return the minimum total cost to end with a single file. One file or none costs 0.

Examples

min_merge_cost([2, 4, 3])      → 14   # (2+3)=5, then (4+5)=9 → 5+9
min_merge_cost([1, 8, 3, 5])   → 30
min_merge_cost([5])            → 0

Starter code

def min_merge_cost(sizes: list[int]) -> int:
    pass

Hints

Hint 1

Bytes in files merged early are rewritten again in every later merge. Which files should be merged first?

Hint 2

Repeatedly pop the two smallest from a min-heap, add their sum to the cost, and push the merged size back.

Where this shows up in data engineering

This is the cost model behind LSM-tree compaction (RocksDB, Cassandra, HBase) and small-file compaction in lakehouses: rewriting big files repeatedly is what makes compaction expensive. Size-tiered compaction merges similar-sized small files first for exactly this reason. The optimal-merge-pattern argument is also how Huffman coding builds its tree.

Solution

import heapq


def min_merge_cost(sizes: list[int]) -> int:
    heap = list(sizes)
    heapq.heapify(heap)                  # O(n)
    cost = 0
    while len(heap) > 1:
        a = heapq.heappop(heap)
        b = heapq.heappop(heap)
        cost += a + b
        heapq.heappush(heap, a + b)
    return cost

Tests

Your solution should pass these:

assert min_merge_cost([2, 4, 3]) == 14
assert min_merge_cost([1, 8, 3, 5]) == 30
assert min_merge_cost([5]) == 0
assert min_merge_cost([]) == 0
assert min_merge_cost([1, 1, 1, 1]) == 8
assert min_merge_cost([10, 20, 30]) == 90

Explanation

Why greedy is optimal: draw the merges as a binary tree; each original file’s bytes are paid once per level above it, so total cost = Σ size × depth. To minimise it, the smallest files should be deepest, so merge the two smallest first. (This is exactly Huffman’s exchange argument.)

Complexity: O(n log n): n - 1 merges, each O(log n) heap work.

K-way merges: if a merge can combine up to k files at once (cost = their total), pad with zero-size files so (n - 1) % (k - 1) == 0, then always merge the k smallest.

Follow-up questions

Real compaction merges up to 10 files at a time. Adapt.

Generalised Huffman: pad with zero-size dummies until (n-1) % (k-1) == 0, then repeatedly pop the k smallest, merge, and push the result.

Why don't production systems always do this exact greedy?

They also care about read amplification (how many files a query must open), write amplification, space amplification and keeping compactions small and incremental. Leveled vs size-tiered compaction trade these off; the greedy only minimises total bytes rewritten.