Quant Memo
Core

Dynamic Programming Basics

Solve a hard problem by solving its smaller versions once, storing the answers, and reusing them. It collapses exponential recomputation into a polynomial pass, and it is the engine behind option-pricing lattices and optimal-execution schedules.

Prerequisites: Big-O Complexity

Dynamic programming (DP) sounds intimidating and is really just one honest idea: if solving a big problem forces you to solve the same smaller problems over and over, solve each small problem once, write the answer down, and look it up next time. That's it. The name is historical and unhelpful; think of it as "careful recursion with a notebook."

The tell that DP applies is a recursion that keeps revisiting the same inputs. Compute Fibonacci naively — fib(n) = fib(n-1) + fib(n-2) — and fib(5) calls fib(3) twice, fib(2) three times, and so on. The call tree has about 2n2^n nodes, so it is exponential. But there are only nn distinct values it ever needs. Remember each one the first time you compute it, and the work drops to O(n)O(n).

The two ingredients

DP works only when a problem has both of these:

  • Optimal substructure — the best answer to the whole is built from the best answers to its parts.
  • Overlapping subproblems — those parts recur, so caching pays off. (If subproblems never repeat, plain divide-and-conquer is enough; there is nothing to cache.)

Given those, you write a recurrence: a formula for the answer to a state in terms of smaller states, plus base cases. Everything else is bookkeeping.

DP = recurrence + a memo. Write the answer to state ss in terms of strictly smaller states, pin down the base cases, then either cache the recursion (top-down / memoization) or fill a table from the base cases up (bottom-up / tabulation). Same answers, same complexity — pick whichever reads more clearly.

Worked example: fewest coins to make change

You have coin denominations {1, 3, 4} and want to make the amount 6 with as few coins as possible. Let dp[a] be the minimum number of coins that sum to a. The recurrence is:

dp[a]=1+minca  dp[ac],dp[0]=0,dp[a] = 1 + \min_{c \,\le\, a}\; dp[a - c], \qquad dp[0] = 0,

which reads: to make amount aa, try using one coin cc, then optimally make the remainder aca - c; take the cheapest choice over all coins. Filling the table from 0 up to 6:

Amount a0123456
dp[a]0121122
best move+1+1+1+3+4+4+1+3+3

So 6 needs just 2 coins (3 + 3). Note how dp[5] reused dp[4] and dp[6] reused dp[3] — each cell was computed once and read by later cells. That reuse is the entire savings.

def min_coins(coins, amount):
    dp = [0] + [float("inf")] * amount     # dp[0] = 0
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                dp[a] = min(dp[a], dp[a - c] + 1)
    return dp[amount]

The complexity is amount × len(coins), i.e. O(nk)O(nk) — polynomial, versus the exponential naive recursion that would re-derive every remainder from scratch.

Always write the recurrence and base cases before touching code. Once the formula is right, top-down (a dictionary/@lru_cache on the recursion) and bottom-up (a loop filling an array) are mechanical translations of it. Most DP bugs are wrong base cases or a loop that reads a cell before it's filled.

Where it shows up in quant-dev interviews

DP is one of the most-asked interview families, and it is genuinely load-bearing on a quant desk. Climbing stairs, longest common subsequence, the knapsack, edit distance, and maximum-subarray are the standard warm-ups. The reason firms lean on it: the binomial option-pricing lattice is textbook DP — the value at each node is built from the two child nodes one step later, filled backward from expiry (Binomial Option Pricing). Optimal execution and inventory schedules are solved by Bellman's equation, the same backward recursion. If you can frame a problem as "value of a state = best immediate choice + value of the resulting state," you are doing the same thing quants do to price and to trade.

The failure mode is state-space blowup. DP is only polynomial if the number of distinct states is polynomial; encode too much into the state and the table becomes astronomically large. Keep the state as small as the recurrence actually needs, and watch for problems where the state is exponential no matter how you slice it — those are NP-hard, and DP won't rescue you.

The habit to build: spot the repeated subproblems, name the state, write the recurrence, decide the fill order, and only then code. Related patterns lean on the same reuse instinct — see Prefix Sums for the one-dimensional case and Monotonic Stack for a different way to avoid re-scanning.

Related concepts

Practice in interviews

Further reading

  • Bellman, Dynamic Programming (1957)
  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (Dynamic Programming)
ShareTwitterLinkedIn