Study Notes
Notesfoundation

Dynamic Programming

The whole craft in one place — what DP is, how to recognise it, and one 3-step framework (state → recurrence → base cases) applied to a dozen worked examples from Fibonacci to stock trading. Every practice problem links to LeetCode.

30 min read·verified Content review 2026·reviewed 2026-08-30

After this you should be able to

  • Explain the core idea in Dynamic Programming
  • Apply Dynamic Programming in a representative exercise
MemoizationTabulationState & Recurrence
DSA · Dynamic Programming · From the ground up

The whole craft in one place: what DP really is, how to recognise it under interview pressure, and a single 3-step framework that solves nearly every DP problem — applied to a dozen worked examples from Fibonacci to stock trading. Every practice problem links straight to LeetCode. Java throughout.

⭐ TRICK the insight that makes it easy ⚠ PITFALL a common mistake / gotcha 💡 TIP interview-relevant aside [+] my own addition
Companion notes Want the same topic taught recursion-first — one 4-step template, ten worked problems unchanged? See Dynamic Programming, From Recursion Up.
I

Notebook Summary (copy by hand)#

Telegraphic and deck-ready — arrows over sentences. Everything here is derivable from the chaptered notes below.

WHAT IS DP?
==========
DP = explore ALL solutions efficiently by reusing sub-answers.
Needs BOTH:
  1. overlapping subproblems  -> same small problem solved many times
  2. optimal substructure     -> best(whole) built from best(parts)
fib(n)=fib(n-1)+fib(n-2)  ->  both hold.  brute O(2^n) -> DP O(n)

vs cousins:
  greedy           -> optimal substructure, NO overlap
  divide & conquer -> splits, but subproblems DON'T overlap
TWO IMPLEMENTATIONS  (any DP can be either)
===========================================
              | TOP-DOWN (memoization)   | BOTTOM-UP (tabulation)
  built with  | recursion + hashmap/arr  | iteration + array
  starts at   | the answer, recurses down| base cases, builds up
  ordering    | don't care (recursion)   | must order subproblems
  speed       | recursion overhead       | usually FASTER
  ease        | EASIER to write          | trickier (loop order)
⭐ recursion WITHOUT memo = not DP, just slow recursion (O(2^n)).
WHEN IS IT DP?  (2 signals)
===========================
1. asks for OPTIMUM or COUNT:
     max/min cost, longest/shortest, # of ways, is-it-possible
2. future choices DEPEND on earlier choices
     (taking X now blocks Y later)  -> kills greedy

signal 1 alone -> could be greedy.  signal 1 + 2 -> DP.
⭐ test: assume greedy works, then hunt a counterexample.
   robber [2,7,9,3,1]: greedy grabs 7, loses the 9. -> DP.
THE FRAMEWORK  (3 pieces, top-down OR bottom-up)
================================================
state   = minimal variables that fully describe a scenario
          (keep only RELEVANT ones — not "sock colour")
1. dp(state) -> answer to the (generalised) problem for that state
2. recurrence  -> relate dp(state) to smaller states   [HARDEST step]
3. base cases  -> states answerable without recursion   [easiest step]

climb stairs: dp(i)=dp(i-1)+dp(i-2); dp(1)=1,dp(2)=2  (fib in disguise)
robber:       dp(i)=max(dp(i-1), dp(i-2)+nums[i])
MULTI-DIMENSIONAL  (more state vars = harder)
=============================================
state-var sources: index i | 2nd index j | limit k
                   | status flag (holding?) | bitmask of used
⭐ STATE REDUCTION: if vars relate, derive one from others.
   mult-ops: right = n-1-(i-left)  -> drop a whole dimension.
ITERATION IN RECURRENCE: choice is a RANGE not 2 options
   -> add an inner for-loop: dp(i)=min over j of (... dp(j) ...)

COMPLEXITY: time = #states x work/state ; space = #states
   #states = product of (range of each state var).
⭐ SPACE TRIM: static recurrence looks back k -> keep k vars. fib O(n)->O(1)
PATTERN CHEAT-SHEET
===================
COUNTING DP        sum states (no max/min); base case != 0.   climb, coin-change-2
STATE BY INACTION  "do nothing" -> dp(i)=dp(i-1).             robber, stock-IV
KADANE             max subarray; cur=max(cur+x,x); O(1).      max-subarray
PATHING (grid)     dp[r][c]=dp[r-1][c]+dp[r][c-1]; move 1-way.unique-paths
DP ON STRINGS      i (& j) index; "is it formable?".          word-break, LCS
⭐ The whole craft in two moves Define the state (what minimal facts describe where you are) and find the recurrence (how the answer for a state is built from smaller states). Base cases, memoization, and the bottom-up rewrite are all mechanical once those two are right.

II

Detailed Study Notes#

You're asked: "How many ways can you climb a 30-step staircase taking 1 or 2 steps at a time?" You write the obvious recursion and for n=30 it's instant; for n=50 your fan spins up; for n=250 the universe ends first. Nothing got harder — you're just re-computing the same staircases billions of times. Dynamic programming is the discipline of never solving the same subproblem twice.

Pick a chapter, then expand only the cards you need.

CHAPTER 1Intro to Dynamic Programming

What DP is, the two ways to implement it, and how to recognise a DP problem in the wild.

What is Dynamic Programming?Concept+

DP systematically explores every possible solution while reusing the answers to the smaller problems inside it. A problem is a DP problem when it has two properties at once:

  • Overlapping subproblems — solving it means solving smaller versions of the same problem, and those repeat. To get F(6) you need F(4); to get F(5) you also need F(4). Same work, asked for twice.
  • Optimal substructure — the answer to the whole is built from the answers to its parts: F(n) = F(n-1) + F(n-2).
Analogy A contractor pricing a house. Optimal substructure = pricing the house by pricing each room and adding them up. Overlapping subproblems = noticing the kitchen and utility room share the same plumbing quote — so you price that quote once and reuse it.
F5 F4 F3 F3 F2 F2 F1 F2 F1 F1 F0 fib(5) recursion tree — F2 is computed 3× and F3 twice. Memoising each the first time collapses the tree to a line.

Contrast the cousins DP is confused with. Greedy has optimal substructure but no overlap — it commits and never reconsiders. Divide and conquer (merge sort) splits into subproblems, but they're disjoint. DP lives precisely where subproblems overlap, and that overlap is what turns exponential brute force into linear work.

Top-down and Bottom-upConcept+

Every DP algorithm can be written two ways — approaching the same staircase from opposite ends.

Bottom-up (tabulation)+

Start at the base cases and iterate forward, filling a table until you reach the answer. For Fibonacci you know F(0)=0, F(1)=1, so you compute upward to F(n). A loop over an array — no recursion.

# Bottom-up: build from the base cases upward
F = array of length (n + 1)
F[0] = 0
F[1] = 1
for i from 2 to n:
    F[i] = F[i - 1] + F[i - 2]
Top-down (memoization)+

Start at the answer, recurse toward the base cases, and cache every result the first time you compute it. Memoising means storing each computed value (hashmap or array) so a repeat call returns the stored answer instead of recursing again.

# Top-down: recurse, but remember what you've already solved
memo = hashmap
Function F(integer i):
    if i is 0 or 1:
        return i
    if i not in memo:
        memo[i] = F(i - 1) + F(i - 2)
    return memo[i]
TOP-DOWN F(n) — answer F(0), F(1) base recurse BOTTOM-UP F(0), F(1) base F(n) — answer iterate Same computation, opposite directions: top-down dives from the answer to the base; bottom-up climbs from the base to the answer.

Which is better?#

Bottom-up wins on speed. No call-stack overhead, and no stack-overflow on deep inputs.
Top-down wins on ease. Recursion sorts out the subproblem order for you, and it mirrors how you reason about the recurrence.
⚠ Pitfall Recursion without memoisation is not DP — it's just slow recursion (O(2ⁿ)). The usual workflow: write top-down first, convert to bottom-up if you need the speed.
When to Use DPConcept+

Recognising DP applies is half the battle. Use two practical signals instead of the slippery theory.

Signal 1 — asks for an optimum or a count: "minimum cost of…", "maximum profit from…", "how many ways to…", "longest possible…", "is it possible to reach…". This narrows it to greedy or DP.

Signal 2 — earlier decisions constrain later ones. Choosing now removes options later. This is what kills greedy and demands DP.

2 7 9 3 1 greedy grabs 7… …but the real prize 9 is now off-limits nums = [2, 7, 9, 3, 1] → best = 2 + 9 + 1 = 12 House Robber: a greedy pass takes 7 over 2 and forfeits the adjacent 9. Earlier choices constrain later ones ⇒ DP.

House Robber (max cash, never two adjacent): optimum is 2+9+1=12, but greedy takes 7 and loses the 9. Longest Increasing Subsequence is similar: taking a 6 early blocks the smaller 3 and 5 that build a longer run.

⭐ Trick — the greedy disproof Unsure whether signal 2 applies? Assume greedy works and hunt for a counterexample where an early choice sabotages a later one. Find one ⇒ DP is needed; can't find one ⇒ greedy may be correct.

III

Interview & Practice Q&A#

Q1 What two properties make a problem a DP problem?

Overlapping subproblems (the same smaller problem is solved repeatedly) and optimal substructure (the whole is built from its parts). Both must hold. Greedy has optimal substructure but no overlap; divide-and-conquer splits but its subproblems don't overlap.

Q2 Top-down vs bottom-up — which would you pick and why?

I'd derive top-down first because recursion mirrors the recurrence and figures out the subproblem order for me. Then, if speed or stack depth matters, convert to bottom-up — iteration avoids call-stack overhead. Same time complexity; the choice is ergonomics and constant factors.

Q3 Is plain recursion the same as dynamic programming?

No. Recursion without caching re-solves overlapping subproblems and stays exponential. It becomes DP only once you memoise (top-down) or tabulate (bottom-up) so each state is computed once. Climbing Stairs is O(2ⁿ) recursive, O(n) the moment you add a memo.

Q4 How do you decide between greedy and DP for a max/min problem?

I check whether earlier decisions constrain later ones. My test: assume greedy works and try to build a counterexample where a locally-best choice blocks a better global outcome. House Robber [2,7,9,3,1] is one — greedy grabs 7 and loses the 9. If such a case exists, greedy is unsafe and I use DP.

Q5 How do you compute the time and space complexity of a DP solution?

Time = number of states × work per state; the state count is the product of each variable's range. With an O(1) recurrence, time = state count; when a state does more work (a substring in Word Break, an inner loop in Job Schedule), multiply that in. Space = stored states, though rolling variables can cut it.

Q6 What is state reduction, and when can you apply it?

Two flavours. (1) If state variables relate, derive one and drop a dimension — Max Score's right=n-1-(i-left) turns 3 variables into 2. (2) If the recurrence is static and reads only the last few states, replace the array with rolling variables — Fibonacci goes O(n)→O(1) space. The first helps both styles; the space trim is mostly bottom-up.

Q7 How is "counting DP" different from a max/min DP?

The recurrence adds states instead of taking max/min, and base cases are usually non-zero (often dp[0]=1). A 0 seed makes the additive recurrence stay 0. Climbing Stairs and Coin Change II are counting DPs.

Q8 When does an inner loop appear inside the recurrence?

When the choice at a state is a range, not a fixed handful. In Minimum Difficulty of a Job Schedule you can assign 1, 2, 3… jobs to today, so the recurrence loops over split points and keeps the best. Coin Change loops over coins. This raises work-per-state and the time complexity.

Q9 Why is Kadane's algorithm considered dynamic programming?

Its current variable holds "the maximum subarray ending at the previous index" — an optimal sub-answer carried forward and extended, i.e. optimal substructure with reuse, expressed in a single rolling variable. That gives O(n) time and O(1) space. It only matters when negatives are present.


IV

Key Takeaways#

  • DP = optimal substructure + overlapping subproblems. Reuse sub-answers; never solve the same state twice.
  • Spot it with two signals: it asks for an optimum/count, and earlier choices constrain later ones (ruling out greedy).
  • One framework, every problem: define dp(state), find the recurrence (hardest), set the base cases (easiest). Identical top-down and bottom-up.
  • Recursion + memo = top-down; loop + array = bottom-up. Write top-down first; convert for speed. Recursion without a memo isn't DP.
  • Complexity is mechanical: time = #states × work/state; space = #states. Count states by multiplying each variable's range.
  • State reduction drops a dimension (derive one variable from others) or trims space (rolling variables for static recurrences).
  • Know the named patterns: counting DP, inaction, Kadane, grid pathing, and string DP cover most of the field.
Every DP problem reduces to two questions: what is the state? (the minimal facts that describe where you are) and what is the recurrence? (how this state's answer is built from smaller ones). Nail those two and the memo, the base cases, and the bottom-up rewrite are all turn-the-crank.