3.7.6 · D2Algorithm Paradigms

Visual walkthrough — Dynamic programming — overlapping subproblems, optimal substructure

1,966 words9 min readBack to topic

This is the visual companion to the parent topic. We start from absolute zero: you only need to know what "adding two numbers" means and what a recipe that calls itself is (a quick refresher on Recursion helps).


Step 1 — What "a subproblem" even looks like

WHAT. Our running example is the Fibonacci sequence. It is defined by one rule:

Let us decode every symbol before we use it:

  • is a machine: you feed it a whole number , it returns one number back.
  • is the input — "which Fibonacci number do you want?"
  • and are the two smaller questions the machine must ask to answer the big one. These smaller questions are the subproblems.
  • The says: combine the two smaller answers by adding them.

We also need the base cases — the smallest questions the machine can answer instantly without asking anything:

WHY define this first. You cannot talk about "repeated work" until you can point at one unit of work — one call to on one input. That single box is the atom of everything below.

PICTURE. Below, one call is drawn as a box labelled by its input. An arrow means "this box asks that box a question."

Figure — Dynamic programming — overlapping subproblems, optimal substructure

Step 2 — Grow the full recursion tree (watch the waste appear)

WHAT. Take the top question and keep applying the rule until every branch reaches a base case ( or ). Each box sprouts two children: its and its .

WHY. We want to count the total work. Total work = total number of boxes in this tree, because each box does one addition. So we must draw the whole thing.

PICTURE. Look at the tree. Now hunt for repeats: how many separate boxes say ? ? Colour-coded below — the same question is being re-asked from scratch on different branches. Nobody remembers anything.

Figure — Dynamic programming — overlapping subproblems, optimal substructure
Recall Count the repeats yourself

In the tree: how many times does get computed? ::: Three separate times. How many times does get computed? ::: Twice.

Those repeated boxes are exactly the overlapping subproblems the parent note warned about. They are the enemy. (This is unlike Divide and Conquer, where every box is a different question and nothing repeats.)


Step 3 — Measure the waste: why the tree is

WHAT. Count boxes by level (distance from the top).

  • Level 0: box ().
  • Level 1: boxes.
  • Level 2: up to boxes.
  • Level : up to boxes.

WHY this tool — powers of two. Every box makes two children, so the population doubles each level down. Whenever a quantity doubles at each step, the honest way to describe its size is a power of two, . That is precisely why we reach for and not, say, : doubling is the fingerprint of exponential growth.

Total boxes , which we write as ==== (the parent's Time Complexity Analysis "big-O" = "grows no faster than").

PICTURE. Each level drawn as a bar; the bar length doubles going down. The total area is the wasted work.

Figure — Dynamic programming — overlapping subproblems, optimal substructure

Step 4 — The key observation: how MANY distinct questions exist?

WHAT. The tree has boxes — but list the distinct inputs appearing anywhere in it: That is only 6 different questions for . In general, for there are exactly distinct inputs: .

WHY this is the whole game. The tree is huge because distinct questions get re-asked. But there are only about genuinely different questions. If we could answer each different question once and reuse it, our total work would be about , not .

PICTURE. The messy tree on the left is squeezed onto a short number line of the only inputs that truly exist, through . Many tree boxes collapse onto the same tick.

Figure — Dynamic programming — overlapping subproblems, optimal substructure

Step 5 — Install the sticky-note (memoization) and watch branches die

WHAT. Add a table memo (a shelf of sticky notes indexed by input). New rule for every call:

  1. If the answer for is already on the shelf, hand it back — do not recurse.
  2. Otherwise compute it once, write it on the shelf, then return it.
def fib(n, memo={}):
    if n < 2: return n
    if n in memo: return memo[n]   # <-- the sticky note: reuse, don't recompute
    memo[n] = fib(n-1, memo) + fib(n-2, memo)
    return memo[n]

Line by line:

  • if n < 2: return n — base cases , answered instantly.
  • if n in memo: return memo[n]the whole point. The second time any question is asked, its entire subtree is skipped.
  • memo[n] = ... — solve once, record the answer.

WHY. In Step 2 the branches for the second , the second/third , etc. were pure repetition. The sticky-note check prunes those repeated branches to a single lookup box. (See Memoization vs Tabulation for the top-down vs bottom-up split.)

PICTURE. The same tree, but every box after the first time a value is computed is greyed-out and replaced by a one-step lookup. The fat exponential tree deflates into a thin chain.

Figure — Dynamic programming — overlapping subproblems, optimal substructure

Step 6 — Count again: the multiplication formula falls out

WHAT. After memoization, each distinct input is computed exactly once. So:

Plug Fibonacci in:

  • work per subproblem (one addition + one shelf write)

WHY a product, not a sum? Because the two quantities are independent: how many different jobs there are, times how long each job takes. This is the master formula for every DP — Fibonacci, Knapsack Problem ( jobs), Longest Common Subsequence ( jobs), even Bellman-Ford ( relaxations). Change the two factors, get any DP's cost.

PICTURE. A grid: columns = distinct subproblems, one cell each; a small clock inside each cell = work per cell. Total time = filling every cell once = (columns) × (per-cell work).

Figure — Dynamic programming — overlapping subproblems, optimal substructure

Step 7 — The degenerate / edge cases (never skipped)

WHAT & WHY. A derivation is only trustworthy if it survives the extremes.

  • or (base cases). The tree is a single box; there is nothing to overlap. Naive and DP both cost . Memoization here is neither faster nor slower — just harmless. Lesson: overlap only appears once the tree is deep enough to branch back onto itself.
  • A problem with NO overlap (e.g. Merge Sort, Divide and Conquer). Every distinct-subproblem count equals the total-box count. The two numbers from Step 4 are the same, so caching saves nothing and only wastes shelf space. DP is pointless here.
  • A problem with NO optimal substructure (e.g. longest simple path in a graph). Combining sub-answers is illegal because sub-paths can reuse a node the whole path already used — so the shelf stores a value that is wrong to reuse. Here memoization is not just useless, it gives a wrong answer.

PICTURE. Three tiny trees side by side: (a) the flat base case with no repeats, (b) a Merge-Sort tree where all leaves are distinct, (c) a path graph where reusing a sub-answer double-counts a node — marked with a red ✗.

Figure — Dynamic programming — overlapping subproblems, optimal substructure
Recall When does the trick actually help?

Caching pays off only when ::: distinct subproblems ≪ total boxes (real overlap) AND sub-answers combine correctly (optimal substructure).


The one-picture summary

Everything above, on a single canvas: the fat tree on the left, the sticky-note shelf in the middle collapsing repeats, and the thin chain on the right — with the master formula printed underneath.

Figure — Dynamic programming — overlapping subproblems, optimal substructure
Recall Feynman: tell the whole walkthrough like a story

We started with a machine that answers "what's the -th Fibonacci number?" by asking two smaller versions of itself. Drawing that out (Step 1–2), we got a bushy tree that doubles at every level, so it has about boxes — a mountain of work — and lots of those boxes ask the exact same question (Step 3). Then came the punchline (Step 4): even though there are boxes, there are only about different questions in the whole tree. So we hand the machine a shelf of sticky notes (Step 5): the first time it answers a question it writes the answer down; every later time it just reads the note instead of re-doing the whole subtree. The bushy tree deflates into a thin chain. Counting again (Step 6): each of the different questions is solved once, each costing one addition, so total time is — that's the master DP formula, (number of different jobs) × (time per job). Finally we checked the edges (Step 7): if the tree never repeats itself, or if the sub-answers can't legally be combined, the sticky-note trick either wastes paper or lies to you. That's the whole soul of dynamic programming in one picture.


Connections

  • Parent topic (Hinglish) — the full treatment.
  • Recursion — the tree we started from.
  • Recursion Trees — the counting technique of Steps 2–3.
  • Memoization vs Tabulation — the shelf, top-down and bottom-up.
  • Time Complexity Analysis — where and come from.
  • Divide and Conquer — the no-overlap edge case.
  • Knapsack Problem, Longest Common Subsequence, Bellman-Ford — the same master formula with different job counts.