Visual walkthrough — DP problems — Fibonacci, coin change (count + min), 0 - 1 knapsack
Step 0 — What is the actual question?
Before any math, picture the situation.
Look at the figure: three items sit outside a bag labelled "capacity ". We must decide, for each, in or out. That is possible subsets — for that is a billion. We want to be cleverer than checking all of them.
Step 1 — Turn the choice into a question we can repeat
WHAT. We attack ONE item at a time. Line the items up as item 1, item 2, …, item . Stand in front of item and ask a single yes/no question: do I put item in the bag?
WHY. A yes/no question has only two branches. If we can solve "best value using items with a bag of size " by only looking at "best value using items ", we have chopped one item off the problem — a strictly smaller problem of the same shape. That is the whole trick of dynamic programming: recursion where every call is a shrunk copy of the original.
PICTURE.
The tree shows the very first fork. The left branch (blue) = "skip item ". The right branch (orange) = "take item ". Notice both children now talk only about items — item is finished with the moment we decide. That is the seed of everything below.
We name the answer to the shrunk problem:
Term by term:
- — how many items we are allowed to consider (a prefix ).
- — how much bag capacity we currently have to spend, .
- — the number we are hunting: the maximum value obtainable under those two limits.
Step 2 — The "skip" branch
WHAT. Suppose we decide not to take item .
WHY. If item never goes in, then whatever value we end up with came entirely from items , and the bag capacity is untouched — still . So the best we can do is exactly the best we could already do with one fewer item and the same room:
- — item is gone from the menu, so we drop back to the previous prefix.
- (unchanged) — skipping costs no weight, so capacity stays the same.
PICTURE.
The blue arrow points straight up one row (from row to row ) and stays in the same column . Straight up = "one fewer item"; same column = "capacity unspent". That vertical arrow is the skip rule.
Step 3 — The "take" branch
WHAT. Now suppose we do take item .
WHY. Two things happen the instant item enters the bag:
- We gain its value .
- We spend of our capacity, leaving only for everything else. And "everything else" must come from items (because item is used up — this is the 0/1 rule: no second copy). So:
- — the reward for putting item in; added once.
- — again we fall back a row, because item can't be reused.
- — the bag now has less room; we ask the sub-problem about the reduced capacity.
Guard condition. This branch only makes sense if the item fits: we need . If the item is too heavy for the current bag, so "take" is impossible and only the skip branch survives.
PICTURE.
The orange arrow goes up one row AND left by columns (from column to column ), then we add on top. Up = fewer items; left = spent capacity; the "" label rides on the arrow = the value collected.
Step 4 — Combine: take the better of the two branches
WHAT. We have two candidate values for : the skip value and the take value. We keep whichever is larger, because we want maximum value.
WHY and not ? These two branches are mutually exclusive decisions for the same cell — item is either in or out, never both. So we do not add them; we choose the winner. answers exactly "which single decision leaves me richer?"
PICTURE.
Both arrows from Steps 2 and 3 now feed into the same target cell (green). The little "" gate keeps the taller bar. Every cell in the whole table is filled by this one gate — that is the entire algorithm.
Base row. Row means "zero items allowed". With no items you can put nothing in, so the value is for every capacity : . That is the floor the whole table stands on.
Step 5 — Fill the whole table, watch a real example
WHAT. Fill row by row, left to right, using only the two arrows. Example from the parent:
WHY row order. Each cell only ever looks at row (both arrows go up). So as long as the previous row is complete before we start the current row, every value we need is already there. No cell is ever read before it is written.
PICTURE.
The colour-shaded grid is the finished table. Trace the bottom-right corner . The winning path (highlighted) came from taking item 2 (weight 3, value 4) and item 3 (weight 4, value 5): weight exactly, value. Because every "take" arrow drops a row, item 2 and item 3 are each used once — the 0/1 promise is kept automatically.
Step 6 — Squeeze two rows into one (and why the loop reverses)
WHAT. Notice each new row reads only the row directly above. So we never need the whole 2D grid — one 1D array dp[w] that we overwrite as we process each item is enough.
WHY reverse the inner loop. When we overwrite a single row in place, the cell we read, , must still hold the old (previous-item) value — never the freshly updated current-item value. If we go left→right, we would update before reading it, so item could sneak in twice. Going right→left (from down to ) guarantees is still the previous item's value when we use it.
PICTURE.
Top strip: forward iteration — the red arrow reads a cell to its left that was already refreshed this round, so item leaks in again (that is unbounded knapsack, see Unbounded Knapsack and Rod Cutting). Bottom strip: backward iteration — the green arrow always reads a cell that is still "old", so each item enters at most once. Same code, one loop direction apart.
Step 7 — Every edge case, shown
WHAT & WHY. A correct derivation must survive the weird inputs. Here they are:
- (bag holds nothing): the take branch needs , impossible for positive weights, so every cell is . Answer . (leftmost panel)
- No items (): the base row is the whole table; answer . (second panel)
- An item heavier than the bag (): its take arrow would leave column , which does not exist — so that item can never be taken and only its skip arrow fires. (third panel — item shown greyed out)
- A zero-value item (): taking it adds but eats weight, so will only ever pick it if it does no harm; it never hurts the optimum. (rightmost panel)
- All items fit together (): every take branch is available, so the optimum is simply — the bag is never the bottleneck.
Because the recurrence is max(skip, take-if-fits) with a base of all-zeros, none of these cases needs special code — they fall out of the same two arrows. That robustness is the payoff of deriving it instead of memorising it.
The one-picture summary
One target cell, two arrows, one gate, one base row of zeros — repeated across a grid, then folded into a single backward-swept array. Everything on this page is that picture.
Recall Feynman retelling — explain the walkthrough to a 12-year-old
Line your toys up. Carry a backpack that can hold a certain weight. Walk to the first toy and ask two things: "If I leave it, how happy am I with the toys behind me and this much room?" — that is looking straight up in the grid. "If I grab it, I get its fun points but my backpack has less room left for the earlier toys" — that is looking up-and-left, then adding the fun points. Keep whichever answer makes you happier. Do this for every toy against every possible amount of room, writing each answer on a sticky note so you never redo it. When you re-use one sticky-note row to save paper, fill it in from the biggest room down to the smallest, so you never accidentally count the same toy twice. The last sticky note, bottom-right, is your best possible happiness.
Recall
Why does the "take" arrow go to row and not row ? ::: Because in 0/1 knapsack each item is used at most once; after taking item we must continue with only the earlier items. Why instead of adding the two branches? ::: Skip and take are two mutually exclusive decisions for the same cell; we pick the better one, we don't do both. In the 1D version, why iterate weight from down to ? ::: So that still holds the previous item's value when read, preventing an item from being taken twice. What is and why? ::: for all — with zero items allowed you can pack nothing, so no value.
See also: Tabulation vs Memoization · Time and Space Complexity · Subset Sum and Partition Problems · Greedy Algorithms · parent topic.