Dynamic Programming I: Memoization

This deck gives the two ingredients that make dynamic programming apply, then shows the naive exponential recursion for Fibonacci and why it recomputes the same values. It presents the two fixes - top-down memoization and bottom-up tabulation - through a repeatable method for defining the state, writing the recurrence, and choosing the base cases, with Fibonacci and climbing stairs fully hand-traced. It targets four real misconceptions: reaching for dynamic programming when the subproblems never actually overlap, a wrong or missing base case that corrupts an entire table, a memo that is written but never checked before recursing, and a state defined too ambiguously to support a clean recurrence.

Subject: CS3000 Algorithms · 134 slides · symbolic lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. What you will be able to do

Objectives

Dynamic programming turns painfully slow recursion into fast, provably correct code by remembering answers you have already worked out. This lesson builds the setup habit from scratch, using two small, fully hand-traced problems. By the end you can:

  1. Recognize the two ingredients that make a problem solvable by dynamic programming: overlapping subproblems and optimal substructure.
  2. Explain why the naive recursive Fibonacci function takes exponential time, by tracing exactly which calls get repeated.
  1. Turn that slow recursion into fast top-down memoization by caching results, and into bottom-up tabulation by filling a table.
  2. Apply the same setup to a second problem, counting ways to climb stairs, and to a fresh variant of it.
  1. Use a repeatable four-step method every time: define the state in words, write the recurrence, pick the base cases, choose a direction.
  2. Spot four real mistakes: using DP where there is no overlap, a wrong base case, a memo that is never checked, and a state defined too ambiguously to write a recurrence for.

2. What survived from Maximum Sum Subarray?

Warm-up

Discussion prompt

Before we open Dynamic Programming I: Memoization: without looking back, what was the main idea of Maximum Sum Subarray, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

The maximum sum subarray problem worked three ways: brute force, divide-and-conquer (with the crossing subarray case), and Kadane's linear scan. Targets confusing a subarray with a subsequence, forgetting the crossing case in divide-and-conquer, resetting Kadane's running sum to zero on an all-negative array, and off-by-one errors when reporting the start/end indices.

3. Toolkit check-in: name them before you look

Concept

Before any new material: cover the screen.

You have named 12 reusable moves so far. Say as many as you can out loud, by number, from memory.

Do not advance until you have actually tried. Getting four of eight is information; skipping the exercise is not.

Here they are. Score yourself.

Today adds one move to this list. Everything else you will need is already above.

The question that starts every proof from here on is not how do I begin. It is which of these applies here?

4. Break it if you can: Toolkit check-in: name them before you look

Counterexample

Discussion prompt

You have named 12 reusable moves so far. Say as many as you can out loud, by number, from memory.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Do not advance until you have actually tried. Getting four of eight is information; skipping the exercise is not.

5. Counting a DP's cost

Picture it

Animation

Shows: Counting a DP's cost — a rendered Manim animation.

Rendered with Manim.

Takeaway: This one formula prices every DP you will meet.

6. What dynamic programming actually is

Concept

Dynamic programming is a technique for solving a problem by solving smaller versions of the same problem, and saving each answer so it is never worked out twice.

That saving step is the whole trick. Everything else in this lesson is about deciding what to save, how to name it, and where to start.

7. By analogy: What dynamic programming actually is

Analogy

Discussion prompt

Explain What dynamic programming actually is by analogy to something with no CS3000 Algorithms in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Dynamic programming is a technique for solving a problem by solving smaller versions of the same problem, and saving each answer so it is never worked out twice.

8. See it: what dynamic programming actually is

Picture it

Animation

Shows: What dynamic programming actually is — a rendered Manim animation.

Rendered with Manim.

Takeaway: The name is historical. The idea is a cache.

9. Ingredient one: optimal substructure

Concept

A problem has optimal substructure when its best answer can be built directly out of the best answers to smaller versions of itself.

optimal substructure — The best solution to the whole problem is assembled from the best solutions to its subproblems, never from a suboptimal one. This is what makes writing a recurrence formula possible at all.

10. Teach it back: Ingredient one: optimal substructure

Explain it

Discussion prompt

Explain Ingredient one: optimal substructure to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

A problem has optimal substructure when its best answer can be built directly out of the best answers to smaller versions of itself.

11. Ingredient two: overlapping subproblems

Concept

A problem has overlapping subproblems when solving it the naive recursive way asks the exact same smaller question more than once.

overlapping subproblems — The same smaller subproblem is requested again and again during a naive recursive solution. Saving its answer the first time, instead of recomputing it every time, is exactly what dynamic programming exploits.

12. Take the definitions apart: optimal substructure vs overlapping subproblems

Definition probe

Sort into buckets

Every line below is part of the definition of optimal substructure or of overlapping subproblems — one or the other, never both. Put each where it belongs.

optimal substructure
The best solution to the whole problem is assembled from the best solutions to its subproblems, never from a suboptimal one.; This is what makes writing a recurrence formula possible at all.
overlapping subproblems
The same smaller subproblem is requested again and again during a naive recursive solution.; Saving its answer the first time, instead of recomputing it every time, is exactly what dynamic programming exploits.
b1
The best solution to the whole problem is assembled from the best solutions to its subproblems, never from a suboptimal one. This is what makes writing a recurrence formula possible at all.
b2
The same smaller subproblem is requested again and again during a naive recursive solution. Saving its answer the first time, instead of recomputing it every time, is exactly what dynamic programming exploits.

13. Overlap is what makes memoization pay

Picture it

Animation

Shows: Overlap is what makes memoization pay — a rendered Manim animation.

Rendered with Manim.

Takeaway: fib(3) appears twice. Without a cache, so does everything beneath it.

14. Spotting DP: look for both ingredients together

Intuition

Neither ingredient alone is enough. A problem can have optimal substructure without ever repeating a subproblem - in that case a memo would sit there empty, never once reused, and buying nothing.

Before reaching for a memo, ask two questions: does the best answer come from smaller best answers, and does the naive recursion ask the same question twice? Only when both answers are yes does caching pay off.

15. A quick baseline: what recursion is

Concept

A recursive function solves a problem by calling itself on a smaller version of that same problem, until it reaches a case small enough to answer directly without calling itself again.

That smallest, directly answerable case is the base case. Every recursive function needs at least one, or it never stops calling itself.

16. Bottom-up fills the same cells

Picture it

Animation

Shows: Bottom-up fills the same cells — a rendered Manim animation.

Rendered with Manim.

Takeaway: Left to right, each cell using only cells already written.

17. Meet the Fibonacci sequence

Concept

The Fibonacci sequence is a list of numbers where each one is the sum of the two numbers before it, starting from 0 and 1.

\[ F(0)=0,\ F(1)=1,\ F(2)=1,\ F(3)=2,\ F(4)=3,\ F(5)=5,\ F(6)=8 \]

18. The naive recursive definition of Fibonacci

Concept

The definition of the sequence translates directly into a recursive function: to find one term, ask for the two terms before it and add them together.

\[ \text{fib}(n) = \text{fib}(n-1) + \text{fib}(n-2) \quad \text{for } n \ge 2 \]

\[ \text{fib}(0) = 0, \qquad \text{fib}(1) = 1 \]

19. Picture the call tree, not just the formula

Intuition

Every call to fib(n) branches into two more calls, fib(n-1) and fib(n-2), and each of those branches again, until the branches finally bottom out at fib(1) or fib(0).

Draw that branching as a tree and a striking pattern jumps out: the same small calls, like fib(2) or fib(1), show up in more than one branch. That repetition is the overlapping subproblem this whole lesson is about.

20. What has to happen first: Tracing the naive recursion for fib(5)

Ranking

Put in order

Put the moves of Tracing the naive recursion for fib(5) into the order they have to happen.

  1. Expand fib(5) into fib(4) and fib(3)
  2. Expand fib(4) into fib(3) and fib(2)
  3. Fully expand every branch down to base cases
  4. Verify the total call count by summing the table

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. Applying the recurrence: fib(5) needs the two terms before it.

21. Tracing the naive recursion for fib(5)

Worked example

Expand fib(5) one call at a time and list every call the naive recursion makes.

Expand fib(5) into fib(4) and fib(3)

Why: Applying the recurrence: fib(5) needs the two terms before it.

\[ \text{fib}(5) \rightarrow \text{fib}(4) + \text{fib}(3) \]

Expand fib(4) into fib(3) and fib(2)

Why: The same rule applies one level down - and notice fib(3) has now appeared as a subproblem of both fib(5) and fib(4).

\[ \text{fib}(4) \rightarrow \text{fib}(3) + \text{fib}(2) \]

Fully expand every branch down to base cases

Why: Continue expanding fib(3) and fib(2) wherever they appear, until only fib(1) and fib(0) remain, and count how many times each call appears anywhere in the tree.

callnumber of times it appears
fib(5)1
fib(4)1
fib(3)2
fib(2)3
fib(1)5
fib(0)3

Verify the total call count by summing the table

Why: Adding every row of the table gives 1 plus 1 plus 2 plus 3 plus 5 plus 3, which is 15 total calls - matching a direct count of the nodes in the full call tree.

\[ 1+1+2+3+5+3 = 15 \text{ total calls} \]

22. Naive recursion recomputes the same work

Picture it

Animation

Shows: Naive recursion recomputes the same work — a rendered Manim animation.

Rendered with Manim.

Takeaway: Two subtrees compute fib(3) independently. That duplication is exponential.

23. The same subproblem, reached by different paths

Concept

fib(3) gets requested from two different places: once directly as part of fib(5)'s expansion, and once as part of fib(4)'s expansion. Both requests ask the exact same question and must get the exact same answer.

A naive recursive call has no memory of what it already solved elsewhere in the tree, so it happily redoes the entire fib(3) computation from scratch the second time, and every deeper call inside it as well.

24. How often is fib(2) really recomputed?

Concept

From the trace, fib(2) appears three separate times in the call tree for fib(5) - once under the first fib(3), once directly under fib(4), and once under the second fib(3).

Every one of those three calls redoes the same two additions from scratch: it calls fib(1) and fib(0) all over again, even though the answer was already found the first time.

25. What feels wrong about this call tree?

Intuition

What feels wrong about this?

Computing the fifth Fibonacci number by the plain recursive definition:

\[ \texttt{fib}(5) \to \texttt{fib}(4), \texttt{fib}(3) \to \texttt{fib}(3), \texttt{fib}(2), \texttt{fib}(2), \texttt{fib}(1) \to \cdots \]

There are only five distinct values that could ever be asked for.

_Plain English only. No notation, no algebra. Just say what bothers you._

The feeling: the tree is enormous but it keeps asking the same handful of questions over and over, and answering each one from scratch every time.

That feeling is the proof. It is not a substitute for the proof — it is the thing the proof writes down.

So the fix does not need a cleverer formula. It needs a place to write answers down. That is the entire content of memoization — and it only works because the number of distinct questions is small.

26. Why naive Fibonacci recursion is exponential time

Concept

Let T(n) count the total number of calls the naive recursion makes to compute fib(n). Each call to fib(n) makes one call to fib(n-1), one call to fib(n-2), and counts itself.

\[ T(n) = T(n-1) + T(n-2) + 1, \qquad T(0)=T(1)=1 \]

This is the Fibonacci recurrence again, in disguise, so the number of calls itself grows like a Fibonacci number - exponentially fast.

\[ T(n) = \Theta(\varphi^{\,n}), \quad \varphi = \frac{1+\sqrt5}{2} \approx 1.618 \]

27. Contrast: a recursion with no repeats at all

Concept

Compare this to computing a factorial recursively: factorial(n) calls factorial(n-1) exactly once, which calls factorial(n-2) exactly once, and so on down to the base case.

That call tree is a single straight line, not a branching tree - every subproblem is asked for exactly once. There is nothing to save, because nothing ever gets asked for twice.

28. When DP applies at all

Picture it

Animation

Shows: When DP applies at all — a rendered Manim animation.

Rendered with Manim.

Takeaway: Divide and conquer has the first without the second — hence no memo table.

29. Something is wrong here: reaching for a memo when nothing overlaps

Anomaly

Predict first

A student writes this, and it looks reasonable:

Merge sort splits an array into a first half and a second half, sorts each half recursively, then merges them. A student adds a memo, hoping it will speed up the recursion the way it did for Fibonacci.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: The first half and second half of the array are always different pieces of data - the recursion never calls itself on the same array slice twice, at any level of the split.

Check for overlap before adding any cache: does the naive recursion ever ask the exact same question twice? For merge sort, no two recursive calls ever receive the same slice of the array.

Why: The first half and second half of the array are always different pieces of data - the recursion never calls itself on the same array slice twice, at any level of the split.

30. Trap: reaching for a memo when nothing overlaps

Trap

The trap

Merge sort splits an array into a first half and a second half, sorts each half recursively, then merges them. A student adds a memo, hoping it will speed up the recursion the way it did for Fibonacci.

Cache each recursive call by the exact array it was given

Why: The first half and second half of the array are always different pieces of data - the recursion never calls itself on the same array slice twice, at any level of the split.

\[ \text{cache hits} = 0 \quad \text{(every subproblem is brand new)} \]

The fix

Check for overlap before adding any cache: does the naive recursion ever ask the exact same question twice? For merge sort, no two recursive calls ever receive the same slice of the array.

Skip the memo and use plain divide-and-conquer instead

Why: With zero possible cache hits, a memo adds bookkeeping overhead and saves nothing - the running time stays exactly what plain divide-and-conquer already gives, order n log n.

\[ \text{running time unchanged}: \ \Theta(n \log n) \]

31. Decode the notation: Trap: reaching for a memo when nothing overlaps

Notation

Annotate

From Trap: reaching for a memo when nothing overlaps — read this one piece at a time. What is each part doing?

On: \( \text{cache hits} = 0 \quad \text{(every subproblem is brand new)} \)

  • The first half and second half of the array are always different pieces of data - the recursion never calls itself on the same array slice twice, at any level of the split.
  • With zero possible cache hits, a memo adds bookkeeping overhead and saves nothing - the running time stays exactly what plain divide-and-conquer already gives, order n log n.

32. The fix: remember what has already been computed

Concept

Fibonacci does have overlapping subproblems, so the fix is straightforward: the first time a call like fib(2) is answered, write its answer down somewhere. The next time fib(2) is requested, look up the answer instead of recomputing it.

33. Naming the fix: memoization

Concept

memoization — Storing the result of each subproblem the first time it is computed, in a lookup table often called a cache or memo, so that every later request for the same subproblem is answered instantly instead of recomputed.

Memoization keeps the exact same recursive structure as the naive version - it just adds one check at the start and one write at the end of every call.

34. When memoization buys nothing

Picture it

Animation

Shows: When memoization buys nothing — a rendered Manim animation.

Rendered with Manim.

Takeaway: Divide and conquer has optimal substructure without the overlap.

35. Why is this step legal: First, check the cache

Explain it to yourself

Discussion prompt

In The shape of a memoized recursive call this move is made:

First, check the cache

Why is that legal? Name the rule or definition it rests on before you read on.

Hint: If you can only say "because that is what you do", the rule is the thing to go and find.

Answer:

If this exact subproblem has already been solved, its answer is sitting in the cache - return it immediately, with no further recursion at all.

36. The shape of a memoized recursive call

Concept

First, check the cache

Why: If this exact subproblem has already been solved, its answer is sitting in the cache - return it immediately, with no further recursion at all.

Otherwise, compute it the normal recursive way

Why: Call the recurrence exactly as the naive version would, recursing into the smaller subproblems it depends on.

Before returning, store the answer in the cache

Why: So that the next time this exact subproblem is requested, the first step's check finds it and skips the recursion entirely.

37. Memoization in pseudocode: three lines added

Concept

The memoized version is the naive recursion with a lookup bolted on the front and a store bolted on the back. Nothing about the recurrence changes.

MEMO-FIB(n, memo)
  if n <= 1
    return n
  if memo[n] is not empty
    return memo[n]
  memo[n] = MEMO-FIB(n - 1, memo) + MEMO-FIB(n - 2, memo)
  return memo[n]

Delete lines 4, 5 and the store on line 6 and you have the exponential version. The recurrence on line 6 is identical in both. All memoization does is guarantee that line 6 runs at most once per value of n.

38. Reading MEMO-FIB line by line

Notation

Every line of MEMO-FIB says one thing. Read the line, then read what it does — not the other way round.

Annotate

  • The base case, untouched by memoization. It was always cheap.
  • The lookup. This is the line that turns exponential into linear, and it must come BEFORE the recursive calls or it saves nothing.
  • The recurrence itself — character for character what the naive version computes.
  • The store. Writing the answer down is what makes the next lookup succeed.
  • There are only n distinct values of n, and line 6 runs at most once for each — so the work is linear, not exponential.

39. Step MEMO-FIB yourself

Invariant

Watch the memo table. Each entry gets written exactly once. Every later call that wants it returns immediately at line 5, without ever reaching line 6.

Step through it

Each time a call returns, say whether it computed or merely looked up.

  1. Line 1: nothing is stored yet
  2. Line 6: compute the left one first
  3. Line 6:
  4. Line 3: the base cases bottom out
  5. Line 6: computed, and written down
  6. Line 6:
  7. Line 4: the right branch of FIB(5) hits the table
  8. Line 5: returned without a single recursive call
  9. Line 6: six values, six computations, not fifteen

40. Each value is computed once, then only read

Picture it

Animation

Shows: MEMO-FIB executing: the current line of pseudocode is highlighted while the data it touches changes.

Rendered with Manim.

Takeaway: The recurrence is unchanged; the lookup on line 4 is what makes each subproblem run once instead of exponentially often.

41. Defining the state for Fibonacci

Concept

Before writing any cache, name exactly what is being remembered. Let the state be a single number, n, and let the cache entry for n store one thing: the Fibonacci value at that index.

\[ \text{memo}[n] = F(n) \]

This is the same recurrence as before - memoization changes nothing about what is being computed, only how many times each piece of it actually gets computed.

42. Choosing the state is the whole design

Picture it

Animation

Shows: Choosing the state is the whole design — a rendered Manim animation.

Rendered with Manim.

Takeaway: Too much state wastes space; too little and the recurrence is wrong.

43. Why is this step legal: Why the sentence has to come first

Explain it to yourself

Discussion prompt

In Step one of every DP, named this move is made:

Why the sentence has to come first

Why is that legal? Name the rule or definition it rests on before you read on.

Hint: If you can only say "because that is what you do", the rule is the thing to go and find.

Answer:

The recurrence is a relationship between entries. If you have not said what an entry means, there is nothing for the recurrence to relate.

44. Step one of every DP, named

Concept

The move: #13 (Name the subproblem).

Say what one entry means, in a sentence with no code in it

Why: memo[i] is the i-th Fibonacci number. That sentence is the state. Write it before the recurrence, always.

Why the sentence has to come first

Why: The recurrence is a relationship between entries. If you have not said what an entry means, there is nothing for the recurrence to relate.

Test for a good sentence: hand it to someone who has not seen the problem, point at one table cell, and ask what number goes there. If they cannot answer, the sentence is not done.

This is the move that will fail you if you skip it, on knapsack and on edit distance especially. It pairs with #12 (Case-split on the last decision): name the entry, then name the last decision, and the recurrence is forced.

45. The DP Setup skeleton

Concept

Every proof of this kind has the same five or six moves in the same order. The order is not something you rediscover each time.

It is on the right. It will stay on the right through the worked examples that follow.

Why this matters: the structure is now handled. You are not spending working memory on what comes next — you are spending all of it on the one hard step.

Step 1 is where every failed DP fails. A subproblem you cannot say in one sentence is a subproblem you cannot write a recurrence for, and steps 2 through 6 will not rescue it.

46. The recurrence, unchanged from before

Concept

The recurrence for the memoized version is identical to the naive one - memoization is not a different formula, it is the same formula plus a lookup step wrapped around it.

\[ \text{memo}[n] = \text{memo}[n-1] + \text{memo}[n-2] \quad \text{for } n \ge 2 \]

47. Base case for the memoized recurrence

Concept

The base cases also carry over unchanged: they are simply pre-filled into the cache before any recursive call happens, so the very first lookup of n equal to 0 or 1 already succeeds.

\[ \text{memo}[0] = 0, \qquad \text{memo}[1] = 1 \]

48. Plan first: Tracing the top-down memoized fib(5)

Step zero

Discussion prompt

Tracing the top-down memoized fib(5) — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.

Hint: It starts with: Recurse all the way down the first branch

Answer:

  1. Recurse all the way down the first branch
  2. Unwind back up, filling in cached values as each call returns
  3. Continue unwinding: fib(4) and fib(5) each reuse a cached value
  4. Verify the call count against the naive trace

49. Tracing the top-down memoized fib(5)

Worked example

Trace every call made computing fib(5) with a cache, noting whether each call is a fresh computation (a cache miss) or an instant lookup (a cache hit).

Recurse all the way down the first branch

Why: fib(5) needs fib(4), which needs fib(3), which needs fib(2), which needs fib(1) and fib(0) - none of these have been seen before, so every one is a cache miss.

callcache statusresult
fib(5)miss - not yet computedpending
fib(4)miss - not yet computedpending
fib(3)miss - not yet computedpending
fib(2)miss - not yet computedpending
fib(1)miss - base case1
fib(0)miss - base case0

Unwind back up, filling in cached values as each call returns

Why: fib(2) combines the now-known fib(1) and fib(0) into 1, and caches it. fib(3) then asks for fib(2) again - but this time it is already cached, so it returns 1 instantly with no recursion.

callcache statusresult
fib(2) computedstored: memo[2] = 11
fib(1) (2nd request)hit - returned instantly1
fib(3) computedstored: memo[3] = 22

Continue unwinding: fib(4) and fib(5) each reuse a cached value

Why: fib(4) needs fib(2) again - a cache hit, returning 1 instantly - then computes and caches memo[4] = 3. fib(5) then needs fib(3) again - a cache hit, returning 2 instantly - then computes memo[5] = 5.

callcache statusresult
fib(2) (2nd request)hit - returned instantly1
fib(4) computedstored: memo[4] = 33
fib(3) (2nd request)hit - returned instantly2
fib(5) computedstored: memo[5] = 55

Verify the call count against the naive trace

Why: This trace made 9 total calls - 6 cache misses (one per distinct index 0 through 5) and 3 cache hits - compared to the naive version's 15 calls for the same fib(5). Every cache hit is a branch of recursion that never had to happen.

\[ 9 \text{ calls (memoized)} \ \text{vs} \ 15 \text{ calls (naive)}, \quad \text{fib}(5) = 5 \text{ either way} \]

50. The cost of forgetting

Picture it

Animation

Shows: The cost of forgetting — a rendered Manim animation.

Rendered with Manim.

Takeaway: Same recursion, same answer. One remembers.

51. Why memoization makes Fibonacci linear time

Concept

There are only n plus 1 distinct subproblems for fib(n): the indices 0 through n. Memoization guarantees each one is actually computed - not just called - exactly once.

\[ \text{time} = \Theta(n) \]

Every call beyond the first for a given index is a cache hit, which does a fixed, small amount of work. That is the entire reason the running time collapses from exponential to linear.

52. The trade: a little space buys a lot of time

Concept

Memoization is not free - the cache itself needs room to store one entry per distinct state, so it costs extra memory that the naive version did not need.

\[ \text{space} = \Theta(n) \quad \text{for the cache} \]

In exchange for that linear amount of space, the running time drops from exponential all the way to linear. That trade - a modest, predictable amount of space for a dramatic amount of time - is the whole economic case for dynamic programming.

53. What rests on this: The trade: a little space buys a lot of time

Socratic

Discussion prompt

Memoization is not free - the cache itself needs room to store one entry per distinct state, so it costs extra memory that the naive version did not need.

Suppose that were not true. What is the first thing in Dynamic Programming I: Memoization that would stop working?

Hint: Follow it one step downstream. The answer is whatever was quietly relying on it.

54. Something is wrong here: a memo that is written but never checked

Anomaly

Predict first

A student writes this, and it looks reasonable:

A student adds a cache to their Fibonacci function, storing each answer after computing it - but forgets to look the answer up before recursing. Every call still recurses fully, no matter what is already sitting in the cache.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Because nothing is ever read back out of the cache before recursing, the call tree branches exactly the same way it did with no cache at all - fib(3) and fib(2) each get fully recomputed at every place they appear.

Check the cache first, at the very top of the function, before doing any recursive work at all - not just after. If this state is already cached, return it immediately and skip the recursion entirely.

Why: Because nothing is ever read back out of the cache before recursing, the call tree branches exactly the same way it did with no cache at all - fib(3) and fib(2) each get fully recomputed at every place they appear.

55. Trap: a memo that is written but never checked

Trap

The trap

A student adds a cache to their Fibonacci function, storing each answer after computing it - but forgets to look the answer up before recursing. Every call still recurses fully, no matter what is already sitting in the cache.

Compute fib(5) with a write-only cache

Why: Because nothing is ever read back out of the cache before recursing, the call tree branches exactly the same way it did with no cache at all - fib(3) and fib(2) each get fully recomputed at every place they appear.

\[ \text{total calls} = 15 \quad \text{(identical to the naive version)} \]

The fix

Check the cache first, at the very top of the function, before doing any recursive work at all - not just after. If this state is already cached, return it immediately and skip the recursion entirely.

Compute fib(5) with a checked cache

Why: Now the second and later requests for fib(3) and fib(2) are caught before any recursion happens, matching the traced result from before: 9 total calls, 6 of them fresh computations.

\[ \text{total calls} = 9 \quad \text{(6 misses, 3 hits)} \]

56. Say it in words: Trap: a memo that is written but never checked

Translation

\( \text{total calls} = 9 \quad \text{(6 misses, 3 hits)} \)

Draw it

Translate both ways. First write the expression above as a sentence with no symbols in it at all. Then cover it, and write your sentence back as notation. If the two versions disagree, the disagreement is the thing to fix.

57. The other direction: bottom-up tabulation

Concept

Top-down memoization starts at the big question, fib(5), and recurses downward toward the base cases, filling in the cache on the way back up. Bottom-up tabulation flips the direction entirely.

Start at the base cases and iterate straight upward, computing fib(2), then fib(3), then fib(4), and so on, using a simple loop with no recursion at all.

58. Why is this step legal: Loop forward, filling each remaining slot from…

Explain it to yourself

Discussion prompt

In The shape of a tabulated solution this move is made:

Loop forward, filling each remaining slot from the recurrence

Why is that legal? Name the rule or definition it rests on before you read on.

Hint: If you can only say "because that is what you do", the rule is the thing to go and find.

Answer:

Every slot is filled using only slots that come before it in the loop, which are already known by the time they're needed.

59. The shape of a tabulated solution

Concept

Make a table with one slot per state

Why: Here, one slot for every index from 0 up to n.

Fill in the base cases directly

Why: These are known facts, not computed from the recurrence - they seed the whole table.

Loop forward, filling each remaining slot from the recurrence

Why: Every slot is filled using only slots that come before it in the loop, which are already known by the time they're needed.

60. Plan first: Filling the Fibonacci table bottom-up

Step zero

Discussion prompt

Filling the Fibonacci table bottom-up — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.

Hint: It starts with: Seed the base cases

Answer:

  1. Seed the base cases
  2. Fill n = 2 through n = 5
  3. Fill n = 6 through n = 8
  4. Verify F(8) against the recurrence one more time

61. Filling the Fibonacci table bottom-up

Worked example

Build the table for F(n) from n equal to 0 up through n equal to 8, one slot at a time.

Seed the base cases

Why: F(0) and F(1) are known facts, not computed - they start the table.

n01
F(n)01

Fill n = 2 through n = 5

Why: Each slot adds the two slots directly before it: F(2) = F(1)+F(0) = 1, F(3) = F(2)+F(1) = 2, F(4) = F(3)+F(2) = 3, F(5) = F(4)+F(3) = 5.

n012345
F(n)011235

Fill n = 6 through n = 8

Why: F(6) = F(5)+F(4) = 5+3 = 8, F(7) = F(6)+F(5) = 8+5 = 13, F(8) = F(7)+F(6) = 13+8 = 21.

n012345678
F(n)01123581321

Verify F(8) against the recurrence one more time

Why: F(8) must equal F(7) plus F(6), and indeed 13 plus 8 is 21 - the table is internally consistent at every slot, not just the last one.

\[ F(8) = 13 + 8 = 21 \]

62. Filling the Fibonacci table bottom-up — line by line

Picture it

Animation

Shows: Each line of the worked example "Filling the Fibonacci table bottom-up", appearing one at a time.

The same working the example does, in the order a tutor would write it.

Takeaway: F(8) must equal F(7) plus F(6), and indeed 13 plus 8 is 21 - the table is internally consistent at every slot, not just the last one.

63. Guess the shape of the answer: Checking bottom-up against top-down and the…

Estimation

Predict first

All three methods answer the exact same question, so they must agree on every value, even though they arrive there completely differently.

Commit before you compute: what does Checking bottom-up against top-down and the naive tree come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: Verify the value is identical across all three rows

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Every method reports fib(5) equal to 5, confirming that memoization and tabulation change only the amount of repeated work, never the final answer - both fewer calls than the naive method's 15.

64. Checking bottom-up against top-down and the naive tree

Worked example

All three methods answer the exact same question, so they must agree on every value, even though they arrive there completely differently.

Compare fib(5) across all three methods

Why: The naive recursion, the memoized top-down version, and the bottom-up table all computed fib(5) earlier in this lesson.

methodcalls madefib(5) value
naive recursion155
top-down memoized95
bottom-up table6 (one fill per slot, n=0..5)5

Verify the value is identical across all three rows

Why: Every method reports fib(5) equal to 5, confirming that memoization and tabulation change only the amount of repeated work, never the final answer - both fewer calls than the naive method's 15.

\[ 5 = 5 = 5 \]

65. Top-down and bottom-up: the same idea, opposite direction

Concept

Both methods rely on the exact same state definition, recurrence, and base cases. The only difference is direction: top-down starts at n and recurses down, caching answers as it returns; bottom-up starts at the base cases and iterates straight up to n.

If you can write one, you can write the other - they are two directions through the same table.

66. Top-down and bottom-up are the same table

Picture it

Animation

Shows: Top-down and bottom-up are the same table — a rendered Manim animation.

Rendered with Manim.

Takeaway: Choose by which order is easier to write, not by which is faster.

67. One practical difference: recursion depth

Concept

Top-down memoization still recurses, so for a large n the call stack grows n calls deep before the first base case returns - on some systems, a large enough n can overflow that stack.

Bottom-up tabulation is a plain loop with no recursion at all, so it never risks a stack overflow, no matter how large n is - one real reason to prefer it once the recurrence is understood.

68. Space optimization: keep only the last two values

Concept

Look closely at the recurrence: computing F(n) only ever needs F(n-1) and F(n-2) - never anything further back. The full table of every value is more than the recurrence actually requires.

\[ \text{prev}, \text{curr} \leftarrow \text{curr}, \ \text{prev}+\text{curr} \]

Rolling just two variables forward, instead of storing the whole table, drops the space used from linear in n down to a constant amount - while the time to compute the answer stays exactly the same.

69. A second problem: climbing stairs

Intuition

You are standing at the bottom of a staircase with a certain number of stairs. On each move you can climb either 1 stair or 2 stairs. The question: in how many distinct ways can you reach the very top?

This sounds nothing like Fibonacci at first glance - there is no obvious sum of two numbers in the problem statement. Watch what happens once the state is defined properly.

70. Defining the state for climbing stairs

Concept

Let the state be a single number: which stair is being reached. Store, for that stair, the number of distinct sequences of 1-and-2 moves that land exactly there.

\[ \text{ways}(n) = \text{number of distinct ways to reach stair } n \]

71. Decision point: the state is named. Now get a recurrence.

Intuition

What move should we make next?

The subproblem sentence for climbing stairs:

\[ \texttt{ways}(n) = \text{the number of distinct ways to climb } n \text{ stairs, taking 1 or 2 at a time} \]

No recurrence yet. Just the sentence.

There is exactly one question that turns that sentence into a recurrence, and you named it last lesson. What is it?

_Look at your toolkit. Say a move number out loud before this slide advances._ A wrong guess is useful. A silent guess is not.

72. Storing the decision, not just the value

Picture it

Animation

Shows: Storing the decision, not just the value — a rendered Manim animation.

Rendered with Manim.

Takeaway: The value alone rarely satisfies the question being asked.

73. Writing the recurrence from the state

Concept

Think about the very last move made to arrive at stair n. It was either a 1-stair step, taken from stair n minus 1, or a 2-stair step, taken from stair n minus 2 - there is no third option.

\[ \text{ways}(n) = \text{ways}(n-1) + \text{ways}(n-2) \quad \text{for } n \ge 2 \]

Every distinct way to reach stair n-1 gives one distinct way to reach stair n by adding a final 1-step, and every distinct way to reach stair n-2 gives one distinct way to reach stair n by adding a final 2-step - together these account for every possible path, with no double counting.

74. Complete the line: Naming the last decision for the stairs

Fill the middle

Fill in the blanks

From Naming the last decision for the stairs — finish the line. Write what belongs on the right of the equals sign before you look.

\texttt\texttt{ways}(n-1) + \texttt{ways}(n-2)(n) = ___

Why: Producing the right-hand side unprompted is the difference between recognising this line and being able to use it. You arrived at stair n. Your last move was either a 1-step from stair n-1, or a 2-step from stair n-2.

75. Naming the last decision for the stairs

Concept

The move: #12 (Case-split on the last decision), then #13 (Name the subproblem).

What was the final step you took?

Why: You arrived at stair n. Your last move was either a 1-step from stair n-1, or a 2-step from stair n-2. There is no third option, which is what makes the case split complete.

Each case is a smaller answer of the same kind

Why: Every way of reaching n-1 extends to exactly one way of reaching n by a 1-step, and likewise for n-2. So you add the two counts.

\[ \texttt{ways}(n) = \texttt{ways}(n-1) + \texttt{ways}(n-2) \]

Check the cases do not overlap

Why: A path whose last move is a 1-step is never a path whose last move is a 2-step, so nothing is double-counted. Always check this — it is where counting DPs go wrong.

The recurrence is Fibonacci, which is a surprise. The way you got there was not a surprise: name the entry, name the last decision, split, write.

76. Base cases for climbing stairs

Concept

Reaching stair 1 has exactly one way: a single 1-stair step. Reaching stair 0 - already at the bottom, no moves needed - also has exactly one way: the empty sequence of moves.

\[ \text{ways}(0) = 1, \qquad \text{ways}(1) = 1 \]

These are facts known before the recurrence ever runs, exactly the same role Fibonacci's base cases played.

77. State the rule before it runs: Tracing the top-down memoized ways(5)

Hypothesis

Predict first

Tracing the top-down memoized ways(5) is about to be worked. State your hypothesis first: which rule or definition decides this one, and what is the first move it forces? Then watch whether the example agrees with you.

Correct: Recurse down to the base cases

Why: ways(5) needs ways(4), which needs ways(3), which needs ways(2), which needs ways(1) and ways(0) - all first requests, so all six are cache misses.

A hypothesis you wrote down is falsifiable; a vague sense of how it will go is not. If the example opens somewhere else, that gap is the thing worth chasing.

78. Tracing the top-down memoized ways(5)

Worked example

Trace ways(5) exactly the way fib(5) was traced earlier - the recurrence has the identical shape, so watch for the identical pattern of cache hits.

Recurse down to the base cases

Why: ways(5) needs ways(4), which needs ways(3), which needs ways(2), which needs ways(1) and ways(0) - all first requests, so all six are cache misses.

callcache statusresult
ways(5)misspending
ways(4)misspending
ways(3)misspending
ways(2)misspending
ways(1)miss - base case1
ways(0)miss - base case1

Unwind, filling in cached values as each call returns

Why: ways(2) combines ways(1) and ways(0) into 1 plus 1, or 2, and caches it. ways(3) then requests ways(1) again - a cache hit, returned instantly - and computes ways(2) plus ways(1), or 2 plus 1, which is 3.

callcache statusresult
ways(2) computedstored: memo[2] = 22
ways(1) (2nd request)hit1
ways(3) computedstored: memo[3] = 33

Continue unwinding to ways(5)

Why: ways(4) requests ways(2) again - a cache hit, returning 2 - and computes 3 plus 2, which is 5. ways(5) then requests ways(3) again - a cache hit, returning 3 - and computes 5 plus 3, which is 8.

callcache statusresult
ways(2) (2nd request)hit2
ways(4) computedstored: memo[4] = 55
ways(3) (2nd request)hit3
ways(5) computedstored: memo[5] = 88

Verify the call count matches the Fibonacci pattern

Why: This trace made 9 total calls - 6 misses, 3 hits - the identical shape found for fib(5), because the recurrence has the identical shape. ways(5) equals 8.

\[ \text{ways}(5) = 8, \quad 9 \text{ total calls} \]

79. The memo pattern, in three lines

Picture it

Animation

Shows: The memo pattern, in three lines — a rendered Manim animation.

Rendered with Manim.

Takeaway: Forgetting to store turns the cache into decoration.

80. What has to happen first: Filling the climbing-stairs table bottom-up

Ranking

Put in order

Put the moves of Filling the climbing-stairs table bottom-up into the order they have to happen.

  1. Seed the base cases
  2. Fill n = 2 through n = 4
  3. Fill n = 5 and n = 6
  4. Verify ways(6) against the recurrence

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. ways(0) and ways(1) are known facts, not computed.

81. Filling the climbing-stairs table bottom-up

Worked example

Build the table for ways(n) from n equal to 0 up through n equal to 6.

Seed the base cases

Why: ways(0) and ways(1) are known facts, not computed.

n01
ways(n)11

Fill n = 2 through n = 4

Why: ways(2) = ways(1)+ways(0) = 1+1 = 2. ways(3) = ways(2)+ways(1) = 2+1 = 3. ways(4) = ways(3)+ways(2) = 3+2 = 5.

n01234
ways(n)11235

Fill n = 5 and n = 6

Why: ways(5) = ways(4)+ways(3) = 5+3 = 8. ways(6) = ways(5)+ways(4) = 8+5 = 13.

n0123456
ways(n)11235813

Verify ways(6) against the recurrence

Why: ways(6) must equal ways(5) plus ways(4), and 8 plus 5 is indeed 13 - the table is consistent at every slot.

\[ \text{ways}(6) = 8 + 5 = 13 \]

82. Filling the climbing-stairs table bottom-up — line by line

Picture it

Animation

Shows: Each line of the worked example "Filling the climbing-stairs table bottom-up", appearing one at a time.

The same working the example does, in the order a tutor would write it.

Takeaway: ways(6) must equal ways(5) plus ways(4), and 8 plus 5 is indeed 13 - the table is consistent at every slot.

83. Guess the shape of the answer: Verifying ways(4) by listing every path…

Estimation

Predict first

Check the table's claim that ways(4) equals 5 by listing every sequence of 1-and-2 moves that sums to exactly 4, without using the recurrence at all.

Commit before you compute: what does Verifying ways(4) by listing every path directly come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: Verify the count matches the table

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Exactly 5 distinct sequences were listed, and no sequence was listed twice - matching ways(4) equal to 5 from both the top-down and bottom-up traces.

84. Verifying ways(4) by listing every path directly

Worked example

Check the table's claim that ways(4) equals 5 by listing every sequence of 1-and-2 moves that sums to exactly 4, without using the recurrence at all.

List every sequence that sums to 4

Why: Four 1-steps; a 1-step, a 1-step, then a 2-step, in every possible order; or two 2-steps.

sequencesum
1+1+1+14
1+1+24
1+2+14
2+1+14
2+24

Verify the count matches the table

Why: Exactly 5 distinct sequences were listed, and no sequence was listed twice - matching ways(4) equal to 5 from both the top-down and bottom-up traces.

\[ 5 \text{ sequences} = \text{ways}(4) \]

85. Verifying ways(4) by listing every path directly — line by line

Picture it

Animation

Shows: Each line of the worked example "Verifying ways(4) by listing every path directly", appearing one at a time.

The same working the example does, in the order a tutor would write it.

Takeaway: Exactly 5 distinct sequences were listed, and no sequence was listed twice - matching ways(4) equal to 5 from both the top-down and bottom-up traces.

86. Something is wrong here: a wrong base case corrupts the whole table

Anomaly

Predict first

A student writes this, and it looks reasonable:

A student reasons that 'zero stairs means zero ways to do anything' and sets ways(0) to 0 instead of 1, while correctly leaving ways(1) at 1.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: ways(2) = ways(1)+ways(0) = 1+0 = 1.

ways(0) counts the empty sequence of moves - already standing on stair 0 with no steps taken is one valid, distinct way to be there, not zero ways.

Why: ways(2) = ways(1)+ways(0) = 1+0 = 1. ways(3) = ways(2)+ways(1) = 1+1 = 2. ways(4) = ways(3)+ways(2) = 2+1 = 3 - every single downstream value is now wrong, not just ways(0) itself.

87. Trap: a wrong base case corrupts the whole table

Trap

The trap

A student reasons that 'zero stairs means zero ways to do anything' and sets ways(0) to 0 instead of 1, while correctly leaving ways(1) at 1.

\[ \text{wrong base case: ways}(0) = 0 \]

Fill the table forward from the wrong base case

Why: ways(2) = ways(1)+ways(0) = 1+0 = 1. ways(3) = ways(2)+ways(1) = 1+1 = 2. ways(4) = ways(3)+ways(2) = 2+1 = 3 - every single downstream value is now wrong, not just ways(0) itself.

n01234
wrong ways(n)01123

The fix

ways(0) counts the empty sequence of moves - already standing on stair 0 with no steps taken is one valid, distinct way to be there, not zero ways.

\[ \text{correct base case: ways}(0) = 1 \]

Fill the table forward from the correct base case

Why: With ways(0) = 1, the same recurrence gives ways(2) = 2, ways(3) = 3, ways(4) = 5 - matching the direct enumeration of all 5 paths to stair 4 confirmed in the previous slide.

n01234
correct ways(n)11235

88. Decode the notation: Trap: a wrong base case corrupts the whole table

Notation

Annotate

From Trap: a wrong base case corrupts the whole table — read this one piece at a time. What is each part doing?

On: \( \text{correct base case: ways}(0) = 1 \)

  • ways(2) = ways(1)+ways(0) = 1+0 = 1. ways(3) = ways(2)+ways(1) = 1+1 = 2. ways(4) = ways(3)+ways(2) = 2+1 = 3 - every single downstream value is now wrong, not just ways(0) itself.
  • With ways(0) = 1, the same recurrence gives ways(2) = 2, ways(3) = 3, ways(4) = 5 - matching the direct enumeration of all 5 paths to stair 4 confirmed in the previous slide.

89. What makes two subproblems 'the same'?

Concept

A cache only helps if it can recognize a repeated question. Two calls are the same subproblem exactly when they share the same state - for Fibonacci and climbing stairs, that state is just the single number n.

The cache key has to capture every piece of information the recurrence actually depends on - nothing more, and nothing less. Leave something out, and different subproblems get confused as if they were the same one; include something irrelevant, and true repeats stop being recognized as repeats.

90. Process: a state that will not recurse

Intuition

Watch me not know the answer. This is what the first two minutes actually look like.

Trying to set up the climbing-stairs DP from a different starting point.

Try: let the entry be the list of all valid climbing sequences of length n

Why: It is certainly well defined, and the answer is its size. Seems like a reasonable place to start.

The table becomes exponential and the recurrence becomes useless

Why: There are exponentially many sequences, so the table cannot be built, and the relationship between the list for n and the list for n-1 is not something you can write in one line.

Dead end. Not a mistake — a move that was worth trying and did not pay off. This happens in most proofs.

Back up. Store the count, not the objects

Why: You were asked how many, not which. Storing the number keeps the entry to one integer and makes the recurrence a single addition.

The lesson generalizes: if the entry is all the things, look for a version where the entry is a number — a count, a maximum, a minimum, a yes or no.

The expert does not see the whole path in advance. The expert tries something, reads the result, and adjusts. That is the skill.

91. The setup method, step 1: define the state in words

Concept

Before writing a single formula, say in plain English exactly what varies from subproblem to subproblem, and exactly what a single table entry stores.

For Fibonacci: 'the index n; store the Fibonacci value at that index.' For climbing stairs: 'the stair n; store the number of distinct ways to reach it.' Both are one sentence - if it takes more than one sentence, the state probably needs to be split or clarified.

92. The setup method, step 2: write the recurrence

Concept

With the state pinned down, ask: what are the actual choices that build this state's answer out of smaller states' answers? List every choice, then combine them - by adding, by taking the best, or whatever the problem calls for.

For both Fibonacci and climbing stairs, the recurrence adds together exactly two smaller states - but the reason differs: Fibonacci's is a definition; climbing stairs' comes from reasoning about the very last move.

93. The setup method, step 3: pin down the base cases

Concept

Find the smallest state or states whose answer is obvious without applying the recurrence at all - these are facts, not computations, and every other entry ultimately depends on them.

Get a base case wrong, and every value built on top of it is wrong too, even though the recurrence itself was applied correctly at every single step - the trap seen earlier with climbing stairs.

94. The setup method, step 4: choose a direction

Concept

Once the state, recurrence, and base cases are settled, decide how to fill in the answers: top-down, letting recursion visit only the states actually needed, with a cache to avoid repeats; or bottom-up, looping forward from the base cases through every state up to the target.

Both directions use the identical state, recurrence, and base cases - this last step changes only the mechanics of filling, never the mathematics being computed.

95. Something is wrong here: a state defined too ambiguously to write a recurrence…

Anomaly

Predict first

A student writes this, and it looks reasonable:

A student tries to define the climbing-stairs state as k, the number of moves used, instead of n, the stair reached: 'let dp[k] be the number of ways using exactly k moves.'

It is wrong. Say what breaks — and say it before you turn the page.

Correct: With steps of size 1 and 2, using exactly k = 2 moves can land on stair 2 (a 1 then a 1), stair 3 (a 1 then a 2, or a 2 then a 1), or stair 4 (a 2 then a 2) - three different destinations, all sharing the same k.

Because dp[k] mixes several different destinations under one label, there is no clean way to write 'ways to reach the top' in terms of it - the state has to pin down exactly one quantity, not a bundle of possibilities.

Why: With steps of size 1 and 2, using exactly k = 2 moves can land on stair 2 (a 1 then a 1), stair 3 (a 1 then a 2, or a 2 then a 1), or stair 4 (a 2 then a 2) - three different destinations, all sharing the same k.

96. Trap: a state defined too ambiguously to write a recurrence for

Trap

The trap

A student tries to define the climbing-stairs state as k, the number of moves used, instead of n, the stair reached: 'let dp[k] be the number of ways using exactly k moves.'

Try to see what dp[k] actually counts

Why: With steps of size 1 and 2, using exactly k = 2 moves can land on stair 2 (a 1 then a 1), stair 3 (a 1 then a 2, or a 2 then a 1), or stair 4 (a 2 then a 2) - three different destinations, all sharing the same k.

\[ k=2 \Rightarrow \text{ reaches stair } 2, \ 3, \text{ or } 4 \text{ - not one fixed stair} \]

The fix

Because dp[k] mixes several different destinations under one label, there is no clean way to write 'ways to reach the top' in terms of it - the state has to pin down exactly one quantity, not a bundle of possibilities.

Define the state as the stair reached instead

Why: dp[n] = number of ways to reach stair n fixes exactly one destination per state, which is what makes dp[n] = dp[n-1] + dp[n-2] well defined: both terms refer to one specific stair each, not a mix of several.

\[ \text{dp}[n] = \text{ways to reach stair } n \quad \text{(one destination, one number)} \]

97. Which of these survive contact with Dynamic Programming I: Memoization?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
You have named 12 reusable moves so far. Say as many as you can out loud, by number, from memory.; Dynamic programming is a technique for solving a problem by solving smaller versions of the same problem, and saving each answer so it is never worked out twice.; A problem has optimal substructure when its best answer can be built directly out of the best answers to smaller versions of itself.
Breaks
Cache each recursive call by the exact array it was given; Compute fib(5) with a write-only cache
sound
These are stated as this lesson states them — each one survives the edge cases Dynamic Programming I: Memoization puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

98. Recognizing when memoization will help, before writing code

Intuition

Write out the recursive definition of the problem in plain words first. Then ask: as this recursion unfolds, could two different paths through it ever ask for the exact same smaller state?

If the answer is yes, the state is worth naming precisely and caching. If the answer is genuinely no - like merge sort splitting into always-different halves - a memo is wasted effort, and plain recursion is already the right tool.

99. Count the states before you code

Picture it

Animation

Shows: Count the states before you code — a rendered Manim animation.

Rendered with Manim.

Takeaway: The state count is the table size, and the table size is the cost.

100. Applying the method fresh: stairs with steps of 1, 2, or 3

Concept

A new variant: the same staircase, but now each move can climb 1, 2, or 3 stairs at a time. Work through all four setup steps on a problem never seen before.

Step 1, the state: let ways3(n) be the number of distinct ways to reach stair n, using moves of size 1, 2, or 3.

101. Decision point: same problem, steps of 1, 2 or 3

Intuition

What move should we make next?

The rule changes. The subproblem sentence does not:

\[ \texttt{ways3}(n) = \text{ways to climb } n \text{ stairs taking 1, 2 or 3 at a time} \]

How many cases now, and how many base cases will you need? Answer both before anyone writes the recurrence.

_Look at your toolkit. Say a move number out loud before this slide advances._ A wrong guess is useful. A silent guess is not.

102. The recurrence and base case for the 3-step version

Concept

Step 2, the recurrence: the last move to reach stair n was a 1-step, a 2-step, or a 3-step, taken from three stairs back, two stairs back, or one stair back respectively.

\[ \text{ways3}(n) = \text{ways3}(n-1) + \text{ways3}(n-2) + \text{ways3}(n-3) \quad \text{for } n \ge 1 \]

Step 3, the base case: ways3(0) is 1, the empty sequence of moves, exactly as before. Any state below stair 0 does not exist, so treat it as contributing 0 ways - there is no way to have already overshot the bottom.

\[ \text{ways3}(0) = 1, \qquad \text{ways3}(n) = 0 \ \text{for } n < 0 \]

103. Teach it back: The recurrence and base case for the 3-step version

Explain it

Discussion prompt

Explain The recurrence and base case for the 3-step version to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

Step 2, the recurrence: the last move to reach stair n was a 1-step, a 2-step, or a 3-step, taken from three stairs back, two stairs back, or one stair back respectively.

104. Plan first: Filling the 3-step table bottom-up

Step zero

Discussion prompt

Filling the 3-step table bottom-up — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.

Hint: It starts with: Fill n = 0 through n = 2

Answer:

  1. Fill n = 0 through n = 2
  2. Fill n = 3 through n = 5
  3. Verify ways3(5) against the recurrence

105. Filling the 3-step table bottom-up

Worked example

Build the table for ways3(n) from n equal to 0 up through n equal to 5, using the out-of-range convention for any negative index.

Fill n = 0 through n = 2

Why: ways3(0) = 1 is the base case. ways3(1) = ways3(0)+ways3(-1)+ways3(-2) = 1+0+0 = 1. ways3(2) = ways3(1)+ways3(0)+ways3(-1) = 1+1+0 = 2.

n012
ways3(n)112

Fill n = 3 through n = 5

Why: ways3(3) = ways3(2)+ways3(1)+ways3(0) = 2+1+1 = 4. ways3(4) = ways3(3)+ways3(2)+ways3(1) = 4+2+1 = 7. ways3(5) = ways3(4)+ways3(3)+ways3(2) = 7+4+2 = 13.

n012345
ways3(n)1124713

Verify ways3(5) against the recurrence

Why: ways3(5) must equal ways3(4) plus ways3(3) plus ways3(2), and indeed 7 plus 4 plus 2 is 13 - the table checks out at its final entry.

\[ \text{ways3}(5) = 7+4+2 = 13 \]

106. Bottom-up needs a legal order

Picture it

Animation

Shows: Bottom-up needs a legal order — a rendered Manim animation.

Rendered with Manim.

Takeaway: Which is the real argument for writing it recursively first.

107. Plan first: Cross-checking ways3(5) by conditioning on the first move…

Step zero

Discussion prompt

Cross-checking ways3(5) by conditioning on the first move instead — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.

Hint: It starts with: Split every path to stair 5 by its first move

Answer:

  1. Split every path to stair 5 by its first move
  2. Substitute the already-filled table values
  3. Verify this matches the last-move recurrence exactly

108. Cross-checking ways3(5) by conditioning on the first move instead

Worked example

The recurrence was built by reasoning about the last move. As an independent check, reason about the first move instead, and confirm the two ways of thinking agree.

Split every path to stair 5 by its first move

Why: A first move of 1 leaves 4 stairs to go, a first move of 2 leaves 3 stairs to go, and a first move of 3 leaves 2 stairs to go - and the count of ways to finish from there is exactly ways3 of whatever remains.

\[ \text{ways3}(5) = \text{ways3}(4) + \text{ways3}(3) + \text{ways3}(2) \]

Substitute the already-filled table values

Why: ways3(4) is 7, ways3(3) is 4, and ways3(2) is 2, from the table filled in the previous slide.

\[ 7 + 4 + 2 = 13 \]

Verify this matches the last-move recurrence exactly

Why: Conditioning on the first move and conditioning on the last move produced the identical sum, 13, confirming ways3(5) is correct regardless of which end of the path is reasoned about first.

\[ \text{first-move total} = \text{last-move total} = 13 \]

109. Cross-checking ways3(5) by conditioning on the… — line by line

Picture it

Animation

Shows: Each line of the worked example "Cross-checking ways3(5) by conditioning on the first move instead", appearing one at a time.

The same working the example does, in the order a tutor would write it.

Takeaway: Conditioning on the first move and conditioning on the last move produced the identical sum, 13, confirming ways3(5) is correct regardless of which end of the path is reasoned about first.

110. What has to happen first: Tracing top-down memoized calls for the 3-step version

Ranking

Put in order

Put the moves of Tracing top-down memoized calls for the 3-step version into the order they have to happen.

  1. Recurse down the first branch to the base case
  2. Unwind, and tally every later request to each index
  3. Verify the misses equal the number of distinct states

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. ways3(4) needs ways3(3), which needs ways3(2), which needs ways3(1), which needs ways3(0) - each of these is a first-time request, so each is a cache miss.

111. Tracing top-down memoized calls for the 3-step version

Worked example

Trace ways3(4) with a checked cache, tallying how many times each distinct index from 0 to 4 is actually requested, and how many of those requests are hits.

Recurse down the first branch to the base case

Why: ways3(4) needs ways3(3), which needs ways3(2), which needs ways3(1), which needs ways3(0) - each of these is a first-time request, so each is a cache miss. Requests for negative indices return 0 immediately and never touch the cache at all.

indexfirst request?result
4misspending
3misspending
2misspending
1misspending
0miss - base case1

Unwind, and tally every later request to each index

Why: As the recursion unwinds, index 0 is requested 2 more times (both hits), index 1 is requested 2 more times (both hits), and index 2 is requested 1 more time (a hit) - each returning instantly instead of recursing again.

indextotal requestsmisseshits
0312
1312
2211
3110
4110

Verify the misses equal the number of distinct states

Why: Summing the misses column gives 1+1+1+1+1, which is 5 - exactly the 5 distinct indices, 0 through 4, that actually exist for this computation. Every one of the 5 hits is recursion that a checked cache made unnecessary.

\[ \text{misses} = 5 = \text{distinct states}, \quad \text{hits} = 5 \]

112. Tracing top-down memoized calls for the 3-step… — line by line

Picture it

Animation

Shows: Each line of the worked example "Tracing top-down memoized calls for the 3-step version", appearing one at a time.

The same working the example does, in the order a tutor would write it.

Takeaway: Summing the misses column gives 1+1+1+1+1, which is 5 - exactly the 5 distinct indices, 0 through 4, that actually exist for this computation. Every one of the 5 hits is recursion that a checked cache made unnecessary.

113. How fast the naive call count really grows

Concept

The naive call count T(n) and the memoized call count both follow simple formulas: memoized calls follow 2n minus 1 for n at least 1, while naive calls follow the much faster-growing T(n) = T(n-1) + T(n-2) + 1.

nnaive callsmemoized calls
5159
62511
1017719

By n equal to 10, the naive version has already made over nine times as many calls as the memoized version - and that gap keeps widening exponentially as n grows further.

114. Watch it run: How fast the naive call count really grows

Pattern

Step through it

Step through How fast the naive call count really grows one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: n is 5
  2. Step 2: n is 6
  3. Step 3: n is 10

115. Time and space complexity, summarized

Concept

Both Fibonacci and climbing stairs, whether solved top-down with a cache or bottom-up with a table, share the identical complexity, because both have exactly n plus 1 distinct states.

\[ \text{time} = \Theta(n), \qquad \text{space} = \Theta(n) \]

That space can be trimmed further to a constant amount whenever the recurrence only ever looks back a fixed number of steps, as seen with the rolling-variable trick - but the time stays linear either way.

116. By analogy: Time and space complexity, summarized

Analogy

Discussion prompt

Explain Time and space complexity, summarized by analogy to something with no CS3000 Algorithms in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Both Fibonacci and climbing stairs, whether solved top-down with a cache or bottom-up with a table, share the identical complexity, because both have exactly n plus 1 distinct states.

117. A memo table is a set of proven facts

Intuition

Every entry in a correctly filled cache or table is a small, already-proven fact - the true answer for a smaller version of the same problem. Reading a cached value is not a shortcut or a guess; it is reusing a proof already completed.

That is exactly why the base cases and the fill order matter so much: every fact has to be built only from facts that are already known, never from a guess still waiting to be confirmed.

118. Memoization: each value computed once

Picture it

Animation

Shows: Memoization: each value computed once — a rendered Manim animation.

Rendered with Manim.

Takeaway: Six cells, six additions. The exponential tree collapses to a line.

119. Why is this step legal: 3. Pin down the base cases

Explain it to yourself

Discussion prompt

In The four-step DP setup recipe this move is made:

3. Pin down the base cases

Why is that legal? Name the rule or definition it rests on before you read on.

Hint: If you can only say "because that is what you do", the rule is the thing to go and find.

Answer:

Identify the smallest state or states whose answer is a known fact, not a computation, and get it exactly right - everything else depends on it.

120. The four-step DP setup recipe

Pattern

1. Define the state in plain words

Why: Name exactly what varies between subproblems and exactly what a single entry stores, before writing any formula.

2. Write the recurrence: what are the actual choices?

Why: List every way a state's answer can be built from smaller states' answers, then combine them the way the problem asks - here, always by adding.

3. Pin down the base cases

Why: Identify the smallest state or states whose answer is a known fact, not a computation, and get it exactly right - everything else depends on it.

4. Choose a direction: top-down with a checked cache, or bottom-up with a table

Why: Both use the identical state, recurrence, and base cases - this step only decides the mechanics of filling them in.

121. Where this shows up: Dynamic Programming I: Memoization

Real world

Discussion prompt

Outside this lesson: where does Dynamic Programming I: Memoization actually turn up? Name one concrete situation — a job, a piece of software someone ships, a decision somebody has to make — and say which part of The four-step DP setup recipe is doing the work in it.

Hint: Vague is the failure mode here. "Engineering" is not a situation; "deciding whether this build is fast enough to ship" is.

Answer:

That deck gives the two ingredients that make dynamic programming apply, then shows the naive exponential recursion for Fibonacci and why it recomputes the same values. It presents the two fixes - top-down memoization and bottom-up tabulation - through a repeatable method for defining the state, writing the recurrence, and choosing the base cases, with Fibonacci and climbing stairs fully hand-traced. It targets four real misconceptions: reaching for dynamic programming when the subproblems never actually overlap, a wrong or missing base case that corrupts an entire table, a memo that is written but never checked before recursing, and a state defined too ambiguously to support a clean recurrence.

122. The DP recipe, every time

Picture it

Animation

Shows: The DP recipe, every time — a rendered Manim animation.

Rendered with Manim.

Takeaway: Step two is where the thinking is; the rest is bookkeeping.

123. Rule out three: Check yourself: counting repeated subproblems

Elimination

Eliminate the wrong options

In the naive recursive call tree for fib(5), how many total times does the call fib(2) get made?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. 3
  • B. 2
  • C. 5
  • D. 6

Survives elimination: A

Why: fib(2) appears three times in the tree: once under the first fib(3), once directly under fib(4), and once under the second fib(3) - matching the hand-traced call-count table from earlier in the lesson.

124. Check yourself: counting repeated subproblems

Check

Recall the naive recursive call tree for fib(5), traced earlier in this lesson.

Check your understanding

In the naive recursive call tree for fib(5), how many total times does the call fib(2) get made?

  • A. 3 (correct)
  • B. 2
  • C. 5
  • D. 6

Answer: A

Why: fib(2) appears three times in the tree: once under the first fib(3), once directly under fib(4), and once under the second fib(3) - matching the hand-traced call-count table from earlier in the lesson.

Why B tempts people
This counts only the fib(2) under fib(4) and the fib(2) under the first fib(3), missing the third occurrence under the second fib(3) on the other side of the tree.
Why C tempts people
This is the call count for fib(1), not fib(2) - the two calls sit right next to each other in the tree and are easy to mix up.
Why D tempts people
This double-counts by also including fib(2)'s own children, fib(1) and fib(0), as if they were additional fib(2) calls.

125. Check yourself: reading a memo trace

Check

Recall the top-down memoized trace of fib(5): 9 total calls, made up of cache misses and cache hits.

Check your understanding

How many of the 9 total calls in the memoized fib(5) trace are cache hits - calls that return instantly with no further recursion?

  • A. 3 (correct)
  • B. 6
  • C. 9
  • D. 0

Answer: A

Why: The trace found 6 cache misses, one fresh computation for each distinct index from 0 to 5, and 3 cache hits - the second requests for fib(3), fib(2), and fib(1) - which together make up all 9 calls.

Why B tempts people
6 is the number of cache misses, the fresh computations - not the number of hits, which is what the question asks for.
Why C tempts people
This assumes every call is a hit once a cache exists, but the first request for each distinct index is always a miss, since nothing is stored yet.
Why D tempts people
This misses that memoization produces its hits while still computing fib(5) itself, not only on some later, separate call.

126. Check yourself: a corrupted base case

Check

A student sets ways(0) equal to 0 instead of 1, while correctly keeping ways(1) equal to 1, and then fills the table using ways(n) = ways(n-1) + ways(n-2).

Check your understanding

Using this flawed base case, what value does the recurrence produce for ways(3)?

  • A. 2 (correct)
  • B. 3
  • C. 1
  • D. 0

Answer: A

Why: With ways(0) wrongly set to 0, ways(2) becomes ways(1) plus ways(0), or 1 plus 0, which is 1; then ways(3) becomes ways(2) plus ways(1), or 1 plus 1, which is 2 - not the true value of 3.

Why B tempts people
3 is the true, correct value of ways(3) - reaching it requires ignoring that the base case was changed and tracing the correct table instead of the flawed one.
Why C tempts people
1 is the flawed table's value for ways(2), one entry earlier - not ways(3), which is the entry actually asked about.
Why D tempts people
This assumes the error collapses every later entry to zero, but ways(1) is still correctly 1, so the flawed table keeps growing above zero, just to the wrong numbers.

127. Answer it before you see the options: Check yourself: spotting an ambiguous…

Prediction

Predict first

Which state definition supports writing a clean recurrence for this problem?

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: dp[n] = the number of ways to reach stair n

Why: dp[n] pins down exactly one destination stair per state, so dp[n-1] and dp[n-2] each refer to one specific, unambiguous quantity - which is exactly what a recurrence needs to combine cleanly.

128. Check yourself: spotting an ambiguous state

Check

Consider the climbing-stairs problem with 1-and-2 stair steps, and the goal of counting the number of distinct ways to reach the top.

Check your understanding

Which state definition supports writing a clean recurrence for this problem?

  • A. dp[n] = the number of ways to reach stair n (correct)
  • B. dp[k] = the number of ways using exactly k moves
  • C. dp[n] = the size of the very last step taken to reach stair n
  • D. dp = the total number of ways across the whole staircase, as one single number

Answer: A

Why: dp[n] pins down exactly one destination stair per state, so dp[n-1] and dp[n-2] each refer to one specific, unambiguous quantity - which is exactly what a recurrence needs to combine cleanly.

Why B tempts people
Using k moves can land on several different stairs depending on the mix of 1-steps and 2-steps used, so dp[k] mixes several destinations under one label instead of naming one.
Why C tempts people
This tracks only the size of one move, not a count of ways to arrive - there is nothing here to add or combine into a running total.
Why D tempts people
Collapsing everything into one single number leaves no smaller version of the problem to recurse into, so no recurrence could be written from it at all.

129. Rule out three: Check yourself: memoized time complexity

Elimination

Eliminate the wrong options

What is the time complexity of the top-down memoized Fibonacci function, in terms of n?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. Theta(n)
  • B. Theta(phi^n), roughly the naive recursion's growth rate
  • C. Theta(n^2)
  • D. Theta(log n)

Survives elimination: A

Why: There are only n plus 1 distinct states, indices 0 through n, and memoization ensures each is computed exactly once with a constant amount of work, making the total time linear in n.

130. Check yourself: memoized time complexity

Check

Recall that fib(n) has exactly n plus 1 distinct states, and memoization guarantees each one is computed exactly once.

Check your understanding

What is the time complexity of the top-down memoized Fibonacci function, in terms of n?

  • A. Theta(n) (correct)
  • B. Theta(phi^n), roughly the naive recursion's growth rate
  • C. Theta(n^2)
  • D. Theta(log n)

Answer: A

Why: There are only n plus 1 distinct states, indices 0 through n, and memoization ensures each is computed exactly once with a constant amount of work, making the total time linear in n.

Why B tempts people
This is the naive recursion's exponential growth rate - the entire point of memoization is to avoid ever reaching this blow-up by never recomputing a state twice.
Why C tempts people
This overcounts the work: each of the n distinct states costs a constant amount of work once its two smaller values are cached, not n units of work per state.
Why D tempts people
This undercounts the work: even with a perfect cache, every one of the n distinct states still has to be computed at least once, so the time cannot be smaller than linear.

131. Toolkit update

Concept

Moves added today:

Moves you reused today:

Moves #13 and #12 are steps 1 and 2 of the skeleton. Everything after them is bookkeeping — which is exactly why the two of them are worth naming.

Full toolkit so far: #1 through #13.

Next session opens with you naming every one of these from memory, before any new material.

132. Break it if you can: Toolkit update

Counterexample

Discussion prompt

Moves #13 and #12 are steps 1 and 2 of the skeleton. Everything after them is bookkeeping — which is exactly why the two of them are worth naming.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Next session opens with you naming every one of these from memory, before any new material.

133. Connect it up: Dynamic Programming I: Memoization

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — The four-step DP setup recipe · Toolkit check-in: name them before you look · What dynamic programming actually is · Ingredient one: optimal substructure · Ingredient two: overlapping subproblems. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

134. What you can do now

Recap

Every dynamic programming problem in this lesson followed the same shape: check for optimal substructure and overlapping subproblems, define a state in words, write a recurrence from that state, pick base cases, then fill the answer top-down with a cache or bottom-up with a table.

Memoization and tabulation are the same idea told in two directions - remembering an answer instead of recomputing it - and that one habit turns exponential recursion into linear time.

mistakethe fix
Memoizing a problem with no repeated subproblemsCheck for overlap first - a memo only pays off when the same state is asked for twice
A wrong or missing base casePin down every base case by hand before trusting a single filled-in row
A memo that is written but never checkedAlways look up the cache before recursing, not just after
A state defined too vaguely to write a recurrenceName exactly what a table entry stores before writing any formula

Sources

  1. Cormen, Leiserson, Rivest, and Stein, Introduction to Algorithms, 3rd ed., Chapter 15 (Dynamic Programming) and Section 15.3 (Elements of dynamic programming / memoization) — MIT Press, 2009.
  2. Kleinberg and Tardos, Algorithm Design, Chapter 6 (Dynamic Programming) — Pearson/Addison-Wesley, 2005.
  3. All recursion trees, call counts, memo traces, tabulation traces, and brute-force enumerations in this deck were recomputed by hand from the stated recurrences and cross-checked against each other for consistency. — Verified 2026-07-18.
  4. Northeastern University CS 3000, Algorithms and Data (Summer 2026) — course page and syllabus — course.ccs.neu.edu/cs3000su26. Sets Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms (3rd ed.) as the textbook; listings follow its conventions.
  5. CS 3000 course notes and midterm references circulated by students — github.com/vigneshsaravanakumar404/CS-3000-Algorithms-Data. Notes are typeset with the algpseudocode package, which is the style the listings in this deck follow.

Want this taught 1-on-1? Alexander tutors CS3000 Algorithms — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108