Coding Interview Patterns Module 9 – Binary search
Maximise the minimum distance
Place k items at given positions so that the closest two are as far apart as possible: sort, check a gap greedily and binary search the widest gap.
What you will learn
- Choose k of n given positions so that the smallest gap between them is as large as possible
- Write the greedy feasibility check after sorting, and prove that it is optimal for a fixed gap
- Search for the last value at which a condition holds without an endless loop
- Rebuild the placement as well as the distance from the search's answer
Before you start
What you’ll learn · Before you start
What you will learn
- Choose k of n given positions so that the smallest gap between them is as large as possible
- Write the greedy feasibility check after sorting, and prove that it is optimal for a fixed gap
- Search for the last value at which a condition holds without an endless loop
- Rebuild the placement as well as the distance from the search's answer
Before you start
On this page
Maximising the minimum distance means choosing k of n given positions so that the two closest chosen ones are as far apart as possible. The search does not try the choices. It guesses a gap d and asks whether k positions can be chosen with every gap at least d: after sorting, one walk from the left answers that, taking each position that is far enough from the last one taken. A smaller gap never makes placing harder, so “k fit with gap d” is true up to some d and false beyond it, and the last d that works can be binary searched. The problem below is an original take on the classic known as aggressive cows; a timed run shows what trying every choice would cost, and a short note names the signal.
The problem: spreading out fast chargers
The problem
A highway authority has n candidate sites for fast chargers at kilometre markers sites[i] along one highway. The
list is in the order the sites were surveyed, not sorted, and two sites may share a marker. It will build exactly k
chargers at k different sites and wants them spread out: return the largest possible distance between the two
closest chargers. Limits: 2 ≤ k ≤ n ≤ 10⁵ and 0 ≤ sites[i] ≤ 10⁹.
sites = [48, 3, 95, 20, 61, 77, 12]and k = 4 give 18: chargers at 3, 48, 77 and 95, whose gaps are 45, 29 and 18.- The same sites with k = 2 give 92, the two ends of the highway.
sites = [5, 5, 5]and k = 2 give 0: two different sites at the same marker.
The brute force: try every choice of k sites
Sort the sites, try each of the C(n, k) choices of k of them, take each choice’s smallest gap, and keep the widest. It is a correct judge for small inputs, and the number of choices explodes:
"""The widest smallest gap, by trying every choice of k sites: the brute force, with its choices counted."""
import math
import time
from itertools import combinations
def widest_by_trying(sites, k):
ordered = sorted(sites)
best, choices = None, 0
for chosen in combinations(ordered, k): # each choice comes out in sorted order
gap = min(b - a for a, b in zip(chosen, chosen[1:]))
best = gap if best is None else max(best, gap)
choices += 1
return best, choices
for sites, k in [([48, 3, 95, 20, 61, 77, 12], 4), ([48, 3, 95, 20, 61, 77, 12], 2), ([5, 5, 5], 2)]:
best, choices = widest_by_trying(sites, k)
print(f"k = {k}: {best} after {choices:,} choices")
sites = [(37 * i) % 1_000 for i in range(26)]
start = time.perf_counter()
best, choices = widest_by_trying(sites, 7)
per_choice = (time.perf_counter() - start) / choices
print(f"26 sites, 7 chargers: {choices:,} choices, about {per_choice * 1e6:.1f} microseconds each")
def duration(seconds):
for unit, size in (("year", 365 * 86_400), ("day", 86_400), ("hour", 3_600), ("minute", 60), ("second", 1)):
if seconds >= size or unit == "second":
value = round(seconds / size)
return f"about {value:,} {unit}{'' if value == 1 else 's'}"
for n, k in [(40, 10), (100, 10), (100_000, 50)]:
count = math.comb(n, k)
shown = f"{count:,} choices" if count < 10**15 else f"{len(str(count))}-digit number of choices"
print(f"{n:,} sites, {k} chargers: {shown}, {duration(count * per_choice) if count < 10**30 else 'far longer than the age of the universe'}") Output
k = 4: 18 after 35 choices k = 2: 92 after 21 choices k = 2: 0 after 3 choices 26 sites, 7 chargers: 657,800 choices, about 1.3 microseconds each 40 sites, 10 chargers: 847,660,528 choices, about 18 minutes 100 sites, 10 chargers: 17,310,309,456,440 choices, about 251 days 100,000 sites, 50 chargers: 186-digit number of choices, far longer than the age of the universe
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 every_choice.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
At a microsecond or more per choice, 40 sites and 10 chargers take minutes, 100 sites take months, and the stated limits are out of reach.
Permutation & Combination Calculator Count C(n, k), the ways to choose k charger sites out of n, for sizes of your own.What to notice
Fix a gap d. If k chargers fit with every gap at least d, the same chargers have every gap at least any smaller d, so “k fit with gap d” holds up to some d and fails beyond it: search for the last d that works. For one d, sort the sites, take the first, and walk along taking each site that is at least d past the last one taken; k fit when the walk takes at least k.
The greedy placement at two gaps
Text description of the diagram
The diagram shows the highway twice as a line with seven candidate sites at kilometre markers 3, 12, 20, 48, 61, 77 and 95. Chosen sites are filled; the others are hollow.
In the first row every gap must be at least 18. Starting at the first site and taking each next site that is at least 18 further, the chargers go to 3, 48, 77 and 95, with gaps of 45, 29 and 18: four chargers fit, and the smallest gap is 18.
In the second row every gap must be at least 19. The same walk takes 3, 48 and 77, but 95 is only 18 past 77, so only three chargers fit.
The note at the bottom says that whatever fits a gap of 19 also fits a gap of 18, so the answer is the last gap at which four chargers fit.
The widest gap by halving, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why taking the leftmost site first is optimal: the proof
- The last true, not the first: two ways to write it
- The search in five languages
- Forgetting to sort, reproduced
- Returning the chargers as well as the distance
- Edge cases, variants and follow-ups
- Edge cases and common bugs
- Variants, with answers
- Follow-up questions, answered
- Practise: the largest smallest share of a reading list
- The largest smallest share of a reading list
- Check yourself
- Interview questions
- To place k chargers at given markers with every gap at least d, you take the leftmost site and then each next site at least d further on. Prove that this places the most chargers possible, and explain why the sites must be sorted first.
- The highway becomes a ring road of length L: markers run from 0 to L − 1, and the gap from the last charger back to the first wraps round. How do you find the widest smallest gap for k chargers, and what does it cost for n = 10⁵ sites?
- Your search for the widest gap passes the samples but hangs on a hidden test with two chargers. After a quick fix it returns 93 instead of 92 on another test. Explain both bugs from the loop's invariant, and write the loop so that neither can happen.
- Key takeaways
5 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
- Sorting Techniques (sorted and list.sort) (Python Software Foundation)
- itertools.combinations (Python Software Foundation)
- ECMAScript language specification: Array.prototype.sort (Ecma International (TC39))
- Array.prototype.sort() (MDN Web Docs)
- Java SE 25 API: Arrays.sort(int[]) (Oracle)
- std::ranges::sort (cppreference.com)
- Package slices: Sort (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress