graphs p vs np ← termit.dev

Dynamic programming, step by step

Dynamic programming is recursion that writes its answers down. When a problem breaks into smaller versions of itself, and the same smaller versions keep coming back, solving each one once and storing it turns exponential work into a table filled in the right order. Each tab fills one table on a small example — the cells, the arrows to where each value came from, and the highlighted line of C++ move together — and every program compiles as-is. You can type your own input into any of them. The last tab is a Google onsite question solved the same way: best traveling route.

Climbing stairs: why the memo matters

O(1.6n) calls → O(n) with a memo

LC 70 asks how many ways there are to climb n stairs, taking 1 or 2 at a time. The last move was either 1 stair or 2, so ways(n) = ways(n − 1) + ways(n − 2): a correct one-line recursion that is hopelessly slow, because it solves the same small problems again and again. Switch the memo off and on and watch the tree: the same answer from 25 calls, or from 11.

the recipe

state
ways(k): the number of ways to climb k stairs.
choice
The last move: 1 stair, leaving ways(k - 1), or 2, leaving ways(k - 2).
base
ways(0) = ways(1) = 1.
order
Smaller k first. The recursion gets that by itself; a table gets it with a loop.
answer
ways(n).

why the memo works

The tree for ways(6) calls ways(4) twice, ways(3) three times and ways(2) five times — Fibonacci numbers again. Those repeats are overlapping subproblems, and ways(k) is the same number whoever asks for it, so it can be stored the first time and handed back after that. With the memo every ways(k) is worked out once, and the other calls for it return at once: 2n − 1 calls.

cost

Without the memo, the call count grows like the Fibonacci numbers themselves, about 1.6n: ways(40) makes 331 million calls. With it, O(n) calls and O(n) memory for the memo and the call stack. The loop below needs no recursion at all, and since it only ever reads the last two values, two variables are enough.

without recursion

Bottom-up fills every answer from the smallest up. It can't overflow the call stack, and it makes the memory easy to cut.

watch out

  • A memo only works if a subproblem's answer is the same whoever asks. If it depends on the path taken so far, that part of the path belongs in the state.
  • The memo needs a “not computed yet” marker that can't be a real answer. 0 is safe here because ways(k) ≥ 1; for answers that can be 0, use −1.
  • Top-down (recursion + memo) only solves the subproblems it actually reaches; bottom-up solves all of them, in an order you choose.
  • The answers grow exponentially, so for big n problems ask for them modulo 109 + 7.

practice

which shape when

the problem saysstatelike
Count or optimize steps toward ndp[k]climbing stairs
A row of things, each taken or not, neighbors interactdp[i]house robber
Reach a total, pieces reusabledp[total]coin change
Best subsequence of one arraydp[i], ending at iLIS
Items and a budget, each item oncedp[i][budget]knapsack
Two strings or two arraysdp[i][j], prefixesLCS, edit distance
A range that gets split or mergeddp[l][r], by lengthburst balloons
A few modes that switch over timedp[day][mode]stock cooldown
Exactly k steps on a graphdp[steps][node]best route, LC 787
Which items are used so fardp[mask]bitmask DP
A treedp[node][flag]tree DP
A DAGtopological ordertopo sort

the recipe

1 · state
The least information that decides a subproblem's answer. Count its values now: that count is the size of the table.
2 · choice
The last decision, and the smaller state each option leaves. The recurrence takes the best (or the sum) over options.
3 · base
The states small enough to answer directly — and a value for “impossible”.
4 · order
Every state after the ones it reads: recursion with a memo, or loops over the table.
5 · cost
States × work per state. Polynomially many states: polynomial time. 2n states: see P vs NP.
words
Overlapping subproblems: the same state is reached many ways. Optimal substructure: a best answer is built from best answers to the smaller states. Memoization is top-down; tabulation is bottom-up.