skip to content

When do overlapping subproblems make a divide-and-conquer split the wrong tool?

level: middleimportance: must knowfreq 60%

answer

  1. Ask what each branch actually touches
  2. Fixed boundary, or a choice of boundary?
  3. Count distinct arguments versus total calls
  4. Exponential calls, polynomially many states
  5. The fix is remembering, not re-splitting

basics

~10 s

Divide-and-conquer assumes the branches touch disjoint work. Once the same subproblem is reachable through many different splits, plain recursion recomputes it exponentially often — that is the signal to memoize or tabulate instead.

solid answer

~50 s

The test is whether your recursion keeps landing on the same subproblem instance. When the branches partition the input — the left half and the right half of a range, two disjoint sets of points — nothing is recomputed and the recurrence over sizes tells the whole story. When the split point itself is a choice, and you try every choice, different outer calls reach identical inner calls; the number of *distinct* subproblems stays polynomial while the number of *calls* explodes. The practical check: sketch the recursion tree two levels deep and look for repeated argument tuples. If you see them, remember the answers — the split-solve-combine skeleton survives, but the cost model changes from a recurrence over sizes to distinct states multiplied by work per state. Most people call that memoized version top-down dynamic programming.

code

pseudocode · 8 lines
pseudocode
// cheapest way to consolidate drops lo..hi into a single route
best(lo, hi):
    if hi - lo <= 1:
        return 0
    m = INFINITY
    for k in lo+1..hi-1:
        m = min(m, best(lo, k) + best(k, hi))
    return m + merge_cost(lo, hi)

go deeper

for a junior

Know the two words that separate the paradigms: independent subproblems point to divide and conquer, repeated subproblems point to storing answers. Being able to say which one you are looking at is enough here.

for a middle

Explain the counting argument out loud: exponentially many calls over polynomially many distinct argument tuples, and memoization collapsing the gap. Be ready to name the state space for a decomposition you just sketched.

for a senior

Show you check for overlap before committing to a design, and that you can state the resulting cost as states times work per state, including the memory that table costs at realistic input sizes.

for a principal

Own the tradeoff between the two shapes under real constraints: a memo table may not fit a memory budget, and a bottom-up pass may be the only version that runs within it. Be ready to defend which one your team should maintain.

## What independence actually means Two conditions hide under the word *independent*: 1. **No result dependency.** No subproblem needs another subproblem's answer as input. If the right half cannot be started until the left half is finished, you have a sequential chain, not two branches. 2. **No repeated instances.** The recursion should not arrive at the same subproblem instance again and again through different paths. This is the condition that fails in dynamic-programming problems, and it is the one people miss. Divide and conquer assumes both. When the second fails, the algorithm is still *correct* — it just does an absurd amount of duplicated work. ## The shape of overlap Overlap almost always appears for a structural reason: **the split point is itself a decision**. In a classic divide-and-conquer decomposition you split at a fixed place — the midpoint of a range, the median coordinate of a point set — and each branch owns a disjoint piece. Nothing can recur, because no other call ever produces those same boundaries. Now suppose the best split is not known in advance, so the algorithm tries every possible split and takes the best result. Every choice of split produces two subranges, and those subranges are shared with the subranges produced by *other* choices, at every level of the recursion. The recursion tree becomes exponentially wide while the set of distinct arguments — pairs of boundaries — is only quadratic. ``` best(0, 8) tries k = 1..7 -> best(0, 3) and best(3, 8) -> best(0, 5) and best(5, 8) both of these recompute best(0, 3) inside ``` That is the signature: distinct arguments are polynomially many, calls are exponentially many, and the ratio between them is exactly the work memoization saves. ## The check you can run before writing code - **Do the branches partition the input?** Halves of a range, disjoint sets of points, the two subtrees of a node — those are partitions, and partitions cannot overlap. Two branches that both read the same middle region, or that are parameterised by a *choice* rather than by a fixed boundary, are suspicious. - **Count distinct subproblems.** Enumerate the argument tuples the recursion can produce. If a single index gives `n` states, a pair of boundaries gives `O(n²)`, a boundary plus a remaining budget gives `O(n * B)`. Compare that count with how many calls the recursion actually makes. - **Draw two levels of the tree.** Repeated argument tuples appearing in different branches is direct evidence, and it takes thirty seconds. ## What to do about it Remember the answers. Add a store keyed by the subproblem's arguments; on entry, return the stored answer if present; on exit, store what you computed. The recursion's structure does not change at all — the same divide, the same conquer, the same combine — but the analysis does. Instead of a recurrence over input sizes, the cost becomes: **(number of distinct subproblems) × (work done per subproblem, excluding recursive calls)** For the boundary-pair example that is `O(n²)` states times `O(n)` split choices each, so `O(n³)` — a wholly different, and finite, universe from the exponential version. The bottom-up variant fills the same table in an order that guarantees dependencies are ready before they are needed, avoiding recursion depth entirely. ## What this does *not* mean It does not mean the recursion was a bad idea. The recursive decomposition is how you *found* the recurrence in the first place, and memoizing it is a mechanical step afterwards. It also does not mean overlap is always fatal to the divide-and-conquer framing: if the repeated subproblems are few, the duplicated work is a constant factor and not worth the storage. The failure mode is specifically the exponential blow-up, where the same handful of states is visited an astronomical number of times. Finally, be precise about the vocabulary in an interview. "Overlapping subproblems" is not a synonym for "the branches read the same input": the two halves of a sorted range both read the same original data, and they still overlap in no subproblem at all. Overlap is about **identical subproblem instances**, not about shared bytes.

  • If you memoize a divide-and-conquer recursion, is it still divide-and-conquer?
    The skeleton is unchanged, but the analysis is not, and most people call the result top-down dynamic programming. The cost stops being a recurrence over input sizes and becomes the number of distinct states times the work per state. Naming it either way is fine in an interview as long as you can state that cost model.
  • Give a thirty-second test you can apply before writing any code.
    Expand the recursion two levels by hand and look for identical argument tuples in different branches. Also ask whether the branches partition the input at a fixed boundary or explore a *choice* of boundary — choices are what generate shared subproblems, partitions never do.
  • Do two recursive calls that read the same input data automatically overlap?
    No. Overlap means identical subproblem *instances*, not shared bytes. Both halves of a range may read from the same underlying data while owning disjoint index ranges, in which case no subproblem is ever computed twice and plain divide and conquer is exactly right.

saying these in an interview costs you the question

  • Treats dynamic programming and divide-and-conquer as the same thing
  • Claims overlapping subproblems only cost a constant factor
  • Never checks whether the branches share subproblems
  • Says shared input data automatically means overlap
  • Assumes exponential call counts mean the problem is intractable

context