How would you find the kth smallest sum over all pairs of two sample lists without materializing every pair?
answer
- never build the pairs at all
- ask how many, not which ones
- counting at or below a threshold is monotone
- search the span of sums, not indices
- smallest threshold whose count reaches k
basics
~20 sBinary search the range of possible sum values rather than the pairs. With both lists sorted, count in one linear sweep how many pairs sum to at most a candidate x, then take the smallest x whose count reaches k.
solid answer
~50 sModel it as end-to-end latency: one sample from a front-hop list, one from a back-hop list, and the end-to-end value is their sum. Materializing all pairs is quadratic and usually impossible at real sizes. Instead, search over **values**: the candidate range runs from the sum of the two minimums to the sum of the two maximums. For a candidate `x`, the predicate is `count(x)` — how many pairs sum to at most `x` — which never decreases as `x` grows, so the range splits into a below-k prefix and an at-least-k suffix. Sort both lists once, then compute `count(x)` with a two-pointer sweep in linear time. The answer is the smallest `x` with `count(x) >= k`, and because `count` only increases at attainable sums, that `x` is itself an achievable pair sum. Total cost is one sort plus a linear sweep per halving.
code
pseudocode · 10 lines// A and B are sorted ascending
countAtMost(A, B, x):
count = 0
j = length(B) - 1
for i in 0..length(A)-1:
while j >= 0 and A[i] + B[j] > x:
j = j - 1
if j < 0: break
count = count + (j + 1)
return countgo deeper
Recall the core move: instead of listing every combination, count how many fall at or below a threshold and slide the threshold until the count reaches the rank you want.
Explain why counting at or below a threshold is monotone, and how two pointers over sorted inputs count qualifying pairs in linear time rather than examining each pair.
Demonstrate the subtleties: that the converged threshold is provably an attainable value, that duplicates force the at-least-k test, and that the one-time sort belongs in the reported cost.
Own the choice under real constraints — when the implicit collection is too large to materialize at all, a counting formulation is the difference between a feasible job and one that cannot run, and that argument is what justifies the extra complexity to a team.
## The problem, and why the obvious route fails A request crosses two hops. You have `n` measured front-hop latencies and `m` measured back-hop latencies, and an end-to-end observation is any front sample plus any back sample. You want the kth smallest end-to-end value across all `n x m` combinations. Building the combinations is `O(n x m)` work and memory before you even sort them — at a few hundred thousand samples per hop that is tens of billions of values, which settles the matter. So do not build them. Search the **values** the answer could take, and use a counting predicate to steer the search. ## The predicate Define `count(x)` as the number of pairs whose sum is at most `x`. It is non-decreasing in `x` by construction: raising the threshold can only admit more pairs, never fewer. That gives the value range the split structure binary search needs — every `x` below the answer has `count(x) < k`, every `x` at or above it has `count(x) >= k` — and the target is the **first** `x` on the at-least-k side. The search range is `[min(A) + min(B), max(A) + max(B)]`. Its width is a property of the measured latencies, not of how many samples there are, so the halving count is the log of that value span. ## Counting without building Sort both lists once, ascending. Then count with two pointers moving in opposite directions: ``` // A and B are sorted ascending countAtMost(A, B, x): count = 0 j = length(B) - 1 for i in 0..length(A)-1: while j >= 0 and A[i] + B[j] > x: j = j - 1 if j < 0: break count = count + (j + 1) return count ``` The invariant is that `j` is the largest index with `A[i] + B[j] <= x`, so every one of `B[0..j]` pairs with `A[i]` under the threshold and contributes `j + 1` at once. Because `A` is ascending, the valid `j` never moves back right as `i` advances, so `j` only ever decreases across the whole run — total work `O(n + m)`, not `O(n x m)`. Once `j` falls below zero, no larger `A[i]` can qualify either, so the loop stops. ## Why the converged value is a real sum This is the step candidates most often distrust: the search walks arbitrary integers in the range, so how can the value it lands on be an actual pair sum? Let `x*` be the smallest `x` with `count(x*) >= k`. Then `count(x* - 1) < k <= count(x*)`, so `count` strictly increased between `x* - 1` and `x*`. But `count` only increases at a value that some pair actually attains — it is a step function whose jumps sit exactly on attainable sums. So at least one pair sums to precisely `x*`, and `x*` is the kth smallest sum. No post-processing, no snapping to the nearest real value. This argument needs integer-valued data. With genuinely real-valued measurements, scale to integers first — latencies in microseconds rather than fractional milliseconds — or accept an epsilon-precision answer, because there is no longer a well-defined `x* - 1`. ## The duplicate trap Use `count(x) >= k`, never `count(x) == k`. Many pairs can share a sum, so `count` can leap from below `k` to well above it in a single step and no `x` ever satisfies equality — a search written around `==` never terminates or returns garbage. Duplicates are the norm here, not an edge case: latency measurements cluster hard. ## Cost Sorting both lists is `O(n log n + m log m)`, done once. Each halving costs one `O(n + m)` sweep, and there are about `log2(R)` halvings where `R` is the value span. Total: `O(n log n + m log m + (n + m) log R)`, with `O(1)` extra space beyond the sort. Set that against `O(n m log(n m))` for materialize-and-sort and the gap is not a constant factor, it is a change of category. ## Recognizing the family The generalizable shape is: a huge implicit collection you can **count** cheaply against a threshold but cannot afford to **enumerate**. Order statistics over pairwise combinations, over a multiplication grid, or over any derived quantity with a monotone counting rule all fit. The interview signal is the sentence "I never build the collection; I only ever ask how many of its members fall at or below a threshold." ## What to watch in an interview State the predicate and its monotonicity before any code. Say why the answer is attainable rather than hoping nobody asks. Name the duplicate handling explicitly. And charge the sort honestly — a candidate who reports the cost as `O((n + m) log R)` and forgets the one-time sort has not actually accounted for the algorithm.
- The search walks arbitrary integers. Why is the value it converges on guaranteed to be a real pair sum?Let `x*` be the smallest value with `count(x*) >= k`. Then `count(x* - 1) < k`, so the count strictly increased at `x*`. The count only increases at values some pair actually attains, so at least one pair sums to exactly `x*`. No rounding or snapping step is needed. The argument requires integer-valued data; with real values you scale to integers or accept an epsilon answer.
- Why test count(x) >= k rather than count(x) == k?Because sums repeat. When several pairs share a value, the count jumps past `k` in a single step and no threshold ever produces equality, so an equality-based search returns nothing or spins. The at-least form is well defined regardless of duplicates and, combined with taking the smallest such `x`, still pins the exact kth value.
- Give the full complexity, and say honestly where the sort fits.One sort of each list, `O(n log n + m log m)`, done once outside the loop. Then about `log2(R)` halvings over the sum range, each an `O(n + m)` two-pointer sweep. Total `O(n log n + m log m + (n + m) log R)` with constant extra space. Materializing and sorting all pairs would be `O(n m log(n m))` time and `O(n m)` memory.
It works like reading a percentile off a histogram: you never list the individual measurements, you only ever ask how many fall at or below a threshold and slide the threshold until the count reaches the rank you want.
saying these in an interview costs you the question
- Materializes all pairwise sums and sorts them
- Says the converged value may not be an attainable sum
- Tests count(x) == k, which duplicates break
- Binary searches indices of the sorted lists instead of values
- Quotes the total cost while forgetting the one-time sort