3.7.8 · D2Algorithm Paradigms

Visual walkthrough — Tabulation (bottom-up DP) — iterative

2,212 words10 min readBack to topic

We use the parent's own example the whole way through:

Everything below is built on one idea from Optimal Substructure: the best answer for the whole bag is made of best answers for smaller bags with fewer items. We just have to write those smaller answers down first — that is tabulation.


Step 1 — Draw the table and name what a box means

WHAT. We make a grid. Rows = "how many items are we allowed to use", columns = "how big is the bag right now".

WHY. Before writing any number, a DP must answer "what does one box mean?" If a box has no clear English meaning, every later step is guesswork. We define:

  • ranges means "you're allowed no items".
  • ranges is a pretend smaller capacity, not always the real . We need these smaller bags because taking an item shrinks the room left.

PICTURE. The empty grid. Look at the labels: every cell is a question ("best value with these many items and this much room?") waiting for an answer.

Figure — Tabulation (bottom-up DP) — iterative

Step 2 — Fill the base row (zero items = zero value)

WHAT. Fill the entire top row with .

WHY. With no items allowed, there is nothing to put in the bag, no matter how big the bag is. So the best value is for every capacity:

  • — the row where zero items are permitted.
  • — empty bag, empty value.

This is the recurrence's seed. Without it, every later cell that "looks back" would read an empty box and the whole build collapses. This is exactly the parent's warning: forget the base case and everything stays 0 or crashes — except here 0 is correct, which is why we set it on purpose.

PICTURE. The green base row of zeros. Every arrow later in the derivation eventually traces back to this row.

Figure — Tabulation (bottom-up DP) — iterative

Step 3 — The choice that makes every other cell: take it or skip it

WHAT. For any cell we imagine standing in front of item with a bag that holds . We have exactly two moves.

WHY. Every item is either in the bag or out. Those two possibilities cover everything and never overlap — so the best answer is the better of the two:

Read each piece slowly:

  • skip: pretend item doesn't exist. Same bag , but now only the first items. The room is untouched.
  • — the reward we grab by taking item .
  • — the room left over after item eats its weight .
  • — the best we can still do in that leftover room with the earlier items.
  • — we don't know which move is better in advance, so we compute both and keep the winner.

PICTURE. The two branches drawn as arrows out of one cell: the skip arrow reaches straight up, the take arrow reaches up-and-left by columns and adds .

Figure — Tabulation (bottom-up DP) — iterative

Step 4 — The guard: what if the item is too heavy?

WHAT. Before the , we must check: does item even fit?

WHY. The "take" branch reads . If , then is negative — a bag with negative room, which is nonsense (and in code an out-of-bounds index). So when the item is too heavy, taking is not an option and only skipping survives:

  • The top line is the degenerate case — no choice, just carry the value from above.
  • The bottom line is the full two-way choice from Step 3.

This is a case we must cover explicitly, or the reader hits a negative index they never saw handled.

PICTURE. Two mini-cells side by side: one where the item is heavier than the bag (only the up arrow is legal, the take arrow is crossed out in red), one where it fits (both arrows legal).

Figure — Tabulation (bottom-up DP) — iterative

Step 5 — Fill row 1 (only item 1 available, weight 1, value 1)

WHAT. Compute for , reading only from the zero-row above.

WHY. Item 1 has weight , value .

  • : bag holds nothing, item weight → doesn't fit → .
  • : fits. .
  • : item still fits, and the leftover room adds nothing new (only one item exists), so each is .

Row 1: .

PICTURE. Row 1 filled, with the arrow for shown: skip-branch reads the above, take-branch reaches up-left one column, reads , adds , wins.

Figure — Tabulation (bottom-up DP) — iterative

Step 6 — Fill row 2 (item 2 now allowed, weight 3, value 4)

WHAT. Compute , reading only from row 1 (already done in Step 5).

WHY. Item 2 has , .

  • : → doesn't fit → copy from above: .
  • : fits. .
  • : fits. .

Row 2: .

Notice : that's item 1 and item 2 together (value ), and the recurrence found it automatically by adding to the leftover-room answer .

PICTURE. Row 2 filled. The star cell shows its take-arrow reaching up-left by columns into , plus the .

Figure — Tabulation (bottom-up DP) — iterative

Step 7 — Fill row 3 and read the answer (item 3, weight 4, value 5)

WHAT. Compute the last row , reading only from row 2. The final answer lives at .

WHY. Item 3 has , .

  • : → doesn't fit → copy row 2: .
  • : fits. .

Row 3: .

The answer is . Here is the subtle, beautiful part: it's a tie. Taking item 3 alone gives ; the previous row already had (items 1+2). The keeps either way — both plans are optimal. This is the tie the parent hinted at, now derived, not asserted.

PICTURE. Full table, bottom-right corner glowing amber as the answer, with both winning routes (item 3 alone; items 1+2) traced.

Figure — Tabulation (bottom-up DP) — iterative

Step 8 — Why the fill order can never go backwards

WHAT. We always filled row by row, top to bottom ( small → large).

WHY. Every cell reads only from row . So when we start row , row is already 100% complete. This is the whole reason tabulation loops small → large, the exact reverse of how memoization recurses down from the top. Break this order — fill row 3 before row 2 — and you'd read empty boxes: the parent's first "steel-manned mistake" made visible.

PICTURE. The dependency picture: an arrow from row (solid, "already done") up into row (being filled), with a red X on any attempt to read a not-yet-filled cell.

Figure — Tabulation (bottom-up DP) — iterative

The one-picture summary

Everything at once: the zero base row seeds the table; each cell picks max(skip, take); "take" hops up-and-left by the item's weight and adds its value; the guard blocks the hop when the item is too heavy; rows fill top-to-bottom; the corner holds the answer .

Figure — Tabulation (bottom-up DP) — iterative
Recall Feynman: the whole walkthrough in plain words

Picture a grid of boxes. The top row is all zeros — "no items, no value, done." Now go down one row at a time. For each box you ask one question: "Item on this row — do I grab it or leave it?" Leaving it means "copy the box directly above me." Grabbing it means "jump up one row, then slide left by however much this item weighs, take whatever value is written there, and add this item's value on top." You keep whichever of the two numbers is bigger. If the item is heavier than the room you're pretending to have, you can't grab it — you just copy from above. Because every box only ever looks at the row above it, you must fill rows from the top down, so the row you look at is already finished. When the last row's last box is filled, that number — the bottom-right corner — is the best value the whole bag can hold. For our items it's 5.


Active recall

What does the cell dp[i][c] mean in words?
The best total value using only the first items with a bag that holds at most capacity .
Why is the whole top row dp[0][c] set to 0?
With zero items allowed there is nothing to put in the bag, so the best value is 0 for every capacity — this seeds the table.
In dp[i][c] = max(skip, take), what is the "take" term?
— grab item 's value, then use the best answer for the leftover room with the earlier items.
Why must we guard with if w_i <= c before the take branch?
Otherwise is negative — a bag with negative room, a nonsense state and an out-of-bounds index.
For our example, what is dp[3][4] and why is it a tie?
5 — item 3 alone gives 5, and items 1+2 also give 5, so the max keeps 5 either way.
Why fill the table top row to bottom row?
Each cell reads only from the row above, so that row must be fully computed first (dependencies before use).

Connections

  • Tabulation (bottom-up DP) — iterative (index 3.7.8) — the parent this page derives in pictures
  • 0-1 Knapsack — the problem we filled out cell by cell
  • Optimal Substructure — why "best whole = best of sub-answers" is legal here
  • Overlapping Subproblems — why writing answers in the table pays off
  • Recurrence Relations — the math of the take/skip formula
  • Space Optimization in DP — collapse this 2D grid to one row (then iterate capacity high→low)
  • Memoization (top-down DP) — the same recurrence, filled in the reverse order

Concept Map

choice

choice

max

max

else only skip

seeds

dependencies first

last cell

dp i c best value

skip item read above

take item up and left plus value

fits only if weight less or equal c

row zero all zeros

fill rows top to bottom

answer dp n W