Coding Interview Patterns Module 16 – Backtracking
Meet in the middle algorithm for n up to 40
Pick gifts whose total is closest to a voucher from up to 40 items: the problem, a measured brute force over all subsets, and the signal for meet in the middle.
What you will learn
- Split a search over 2ⁿ subsets into two searches over 2^(n/2) half subsets, and combine them
- Find the subset total closest to a target for up to 40 values
- Combine the halves with sorting and binary search, or with one sweep of two pointers
- Prove that the best pair of half totals is the best subset, checking partners on both sides
- Measure the enumeration of 2²⁰ totals in Python and C++, and size the memory it needs
Before you start
What you’ll learn · Before you start
What you will learn
- Split a search over 2ⁿ subsets into two searches over 2^(n/2) half subsets, and combine them
- Find the subset total closest to a target for up to 40 values
- Combine the halves with sorting and binary search, or with one sweep of two pointers
- Prove that the best pair of half totals is the best subset, checking partners on both sides
- Measure the enumeration of 2²⁰ totals in Python and C++, and size the memory it needs
Before you start
On this page
The meet in the middle algorithm solves a search over every subset of n items by splitting the items into two halves, searching each half on its own, and combining the two lists of results. For n = 40 that replaces 2⁴⁰, about 10¹², subsets with two lists of 2²⁰, about a million totals each, which a laptop handles in seconds. The classic case is choosing items whose total comes as close as possible to a target.
The problem: gifts closest to a voucher
The problem
A gift shop lists n items with whole-number prices, and you hold a voucher worth V. Choose any set of items, each
at most once, whose total is as close to V as possible, above or below. Write closest_subset_sum(values, target),
which returns the smallest possible abs(total − target); the empty set counts, with a total of 0. Limits:
1 ≤ n ≤ 40, prices up to 10⁹, and 0 ≤ V ≤ 4 × 10¹⁰.
- Prices 9, 14, 21, 35 and 52, voucher 60: the answer is 1. No set totals 60, and 9 + 52 = 61.
- Prices 400, 250, 175, 990, 610, 330 and 85, voucher 1000: 10, from 990 alone or from 400 + 610.
- Prices 12, 7 and 30, voucher 5: 2, from the 7 alone, which beats the empty set’s 5.
The first example split into two halves
Text description of the diagram
The five prices 9, 14, 21, 35 and 52 are split into a first half, 9 and 14, and a second half, 21, 35 and 52.
- The left column lists the totals of every set of the first half: 0 (no item), 9, 14 and 23.
- The right column lists the totals of every set of the second half in increasing order: 0, 21, 35, 52, 56, 73, 87 and 108.
Every set of the five prices is one set from each half, so its total is one left total plus one right total: 4 times 8 makes all 32 sets. A line joins the left total 9 and the right total 52, highlighted: together 61, the total closest to the voucher of 60, one away. No set totals exactly 60.
The brute force, and why it fails at these sizes
Compute the total of every subset, each from the subset that lacks its lowest item, and keep the closest:
# The brute force: the total of every subset, each built from the subset one smaller, and the closest one kept.
import time
def closest_brute(prices, voucher):
totals = [0] * (1 << len(prices)) # totals[mask]: the total of the items whose bits are set in mask
best = abs(voucher)
for mask in range(1, len(totals)):
low = mask & -mask # the lowest item of the subset
totals[mask] = totals[mask ^ low] + prices[low.bit_length() - 1]
best = min(best, abs(totals[mask] - voucher))
return best
print(closest_brute([9, 14, 21, 35, 52], 60))
print(closest_brute([400, 250, 175, 990, 610, 330, 85], 1000))
print(closest_brute([12, 7, 30], 5))
print(" n subsets time")
prices = [100_000_000 + (i * 7_919_003) % 900_000_000 for i in range(40)]
for n in range(14, 21, 2):
start = time.perf_counter()
closest_brute(prices[:n], sum(prices[:n]) // 3)
seconds = time.perf_counter() - start
print(f"{n:2} {2 ** n:13,} {seconds:5.2f} s")
print(f"40 items: 2 ** 40 = {2 ** 40:,} subsets, about {seconds * 2 ** 20 / 86400:.0f} days at that speed") Output
1 10 2 n subsets time 14 16,384 0.00 s 16 65,536 0.01 s 18 262,144 0.05 s 20 1,048,576 0.71 s 40 items: 2 ** 40 = 1,099,511,627,776 subsets, about 9 days at that speed
This output changes from run to run: the times depend on the computer and on what else it is doing
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The three answers are right. Each extra item doubles the work, so 20 items, a million subsets, already take a noticeable fraction of a second in Python, and 40 items mean 1,099,511,627,776 subsets, days even at that speed. A compiled language at roughly 10⁸ simple steps a second still needs hours. Pruning cannot rescue it either: with a target to approach from both sides, no partial total is hopeless.
What to notice
- Every subset is two subsets. Its items in the first half form one subset and its items in the second half another, so its total is a total of the first half plus a total of the second.
- Half of 40 is small. Each half of 20 items has only 2²⁰ = 1,048,576 subsets.
- For one left total the best partner is fixed: the right total nearest to V minus that left total.
Four techniques follow:
- Splitting into halves lists every subset total of each half, about 2 × 2^(n/2) totals instead of 2ⁿ.
- Sorting one half lets a binary search find, for each left total, the nearest right total.
- Two pointers sweep both sorted halves once, from opposite ends, and pass every pair that could be closest.
- Merging by doubling builds a half’s totals already sorted, so no separate sort is needed.
Splitting the items into two halves, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why the best pair of half totals is the best subset
- Enumerating 2²⁰ totals, timed in Python and C++
- Sorted totals without sorting: the doubling merge
- The code in five languages
- How much work it does
- Edge cases and common bugs
- Variants: counting within a budget, exact totals and a knapsack
- Follow-up questions
- Exercise: the closest total for up to 40 values
- The subset total closest to a target, for up to 40 values
- A practice set with sample answers
- Check yourself
- Interview questions
- What does meet in the middle gain over trying every subset of n items, and what does it cost?
- Given 40 integers of up to 10⁹ and a target, return the subset total closest to the target. Walk through your plan with its time and memory in numbers, and name the bugs your tests will look for.
- Four lists hold 2,000 integers each. Count the ways to pick one number from each list so that the four add up to zero. Trying every choice is 1.6 × 10¹³ combinations. How do you count them, and what does it cost?
- A planner chooses among 36 parcels, each with a weight of up to 10⁹ grams and a value, for one truck that can carry 10¹⁰ grams, and wants the most value. A table over weights is impossible. Design the search, and say how you keep its lists small.
- Key takeaways
7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 22 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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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
- Computing partitions with applications to the knapsack problem (E. Horowitz and S. Sahni, Journal of the ACM 21(2), 1974) (Association for Computing Machinery)
- NIST Dictionary of Algorithms and Data Structures: binary search (National Institute of Standards and Technology (NIST))
- bisect: array bisection algorithm (The Python Standard Library) (Python Software Foundation)
- collections.Counter (The Python Standard Library) (Python Software Foundation)
- TypedArray.prototype.sort(): numeric order by default (MDN Web Docs)
- Number.MAX_SAFE_INTEGER (MDN Web Docs)
- Java SE 25 API: Arrays.binarySearch(long[], long) (Oracle)
- std::lower_bound (cppreference.com) (cppreference.com)
- Package sort: Search (The Go Authors)
- The Go Programming Language Specification: for statements with range clause (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress