35

Dynamic Programming

Solve each small problem once, write the answer down, and build the big answer from the small ones.

intermediate~170 min11 simulations21 problems

▶ guided lesson⚡ 1-minute summary↻ 15 recall cards

before this: Recursion · Big-O · Arrays & Strings · Backtracking

01 · why it exists

Why this exists, and the weight it carries

since1952–57

Dynamic programming

Richard Bellman (RAND Corporation)

Richard Bellman studied multistage decision problems at the RAND Corporation; his 1952 PNAS paper is titled "On the Theory of Dynamic Programming", and his 1957 book Dynamic Programming spread the method. In his 1984 autobiography he recalled choosing the name to shield the work: the Secretary of Defense, Wilson, had "a pathological fear and hatred of the word, research", while "dynamic" was a word even a Congressman couldn't object to. The dates don't quite fit — Charles Wilson took office in January 1953, after the 1952 paper already used the name — so read it as Bellman's own recollection.

the problem it solvedMultistage decisions — choosing a sequence of actions over time — where trying every sequence is hopeless, but each stage depends only on the state you are in.

its jobs today

  • Routing: Bellman–Ford and Floyd–Warshall compute shortest paths as DP over "paths with at most k edges" or "through the first k vertices".
  • Text tools: spell-check suggestions rank words by edit distance, a two-sequence DP table; diff solves the same table's longest-common-subsequence problem with faster, specialised algorithms.
  • Speech and text recognition: the Viterbi algorithm, a DP over time steps, picks the most likely sequence of hidden states.

the weight it carries

Illustrative: Climbing Stairs for n = 45. Plain recursion makes 2·F(46) − 1 = 2 × 1,836,311,903 − 1 = 3,672,623,805 calls (≈ 3.7 billion). The two-variable DP does 44 additions.

without it

Every "best way" or "how many ways" question with a choice at each step — routes, edits, budgets — would stay exponential: 45 stairs already needs billions of calls.

when · why · how

when
A problem asks for a count, a best value or a yes/no over a sequence of choices, and the brute-force recursion repeats the same arguments.
why
Time becomes states × work per state (Climbing Stairs: O(2ⁿ) → O(n)). You pay memory for the table — often squeezable to one row or two variables.
how
Write the recursion, then cache it in an int[]/long[] filled with −1 (top-down), or fill dp[0..n] with a loop from the base cases up (bottom-up). Memoization's own story (Michie, 1968) is in the Recursion chapter.

02 · plain english

The idea in 30 seconds

Many recursive solutions ask the same small question again and again. Fibonacci(40) calls fib(2) over 60 million times.

DP = recursion + a notebook: the first time you solve a subproblem, write the answer down; every later time, just read it.

Once you can say "the answer for n is built from the answers for smaller n", you can fill a table from small to big — no recursion needed.

picture this

An Excel sheet where cell B7 is =B5+B6. Excel doesn't re-add the whole column every time you look at B7: every cell already holds its value, and a new cell only reads cells that are finished. A DP table is that sheet — one cell per small question, each filled once, each built from cells above it or to its left.

03 · remember

What you must know

  • DP applies when a problem has overlapping subproblems (the same sub-question repeats) and optimal substructure (the best answer is built from best answers to sub-questions).
  • The ladder: plain recursion (exponential) → memoization / top-down (cache results) → tabulation / bottom-up (fill a table in order) → space optimisation (keep only the rows you still read).
  • The 5-step recipe: 1) state — what does dp[i] (or dp[i][j]) mean in one sentence; 2) transition — how it's built from smaller states; 3) base cases; 4) order — fill so every state you read is already done; 5) answer — which cell holds it.
  • Time ≈ number of states × work per state. Fibonacci: n states × O(1). LCS: m·n states × O(1). LIS: n states × O(n) = O(n²).
  • 0/1 knapsack (each item once) loops capacity downward in a 1D array; unbounded (reuse allowed) loops upward. That single loop direction is the whole difference.
  • Two-string DP (LCS, edit distance): dp[i][j] = answer for the first i chars of one and the first j chars of the other. Compare the last characters and look at the 3 neighbours: ↖, ↑, ←.
  • Grid DP: dp[r][c] from the cell above and the cell to the left (and sometimes up-left). Edges have only one neighbour — they are your base cases.
  • Interval DP (palindromes, burst balloons): dp[i][j] for a range, filled from short intervals to long ones. Often O(n³).
  • Counting problems use +, optimisation problems use min/max, feasibility problems use ||. The skeleton is the same.
  • If dp[i] only reads dp[i-1] and dp[i-2], you only need two variables. If a 2D row only reads the previous row, you only need one or two rows.

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

Why plain recursion explodes

The same fib(k) is computed again and again (red). The counter shows how often each value is recomputed.

loading simulation…
sim

Memoization collapses the tree

Same recursion, plus a notebook. Repeated calls become instant memo hits (green leaves) — whole subtrees disappear.

loading simulation…
sim

Bottom-up: a 1D table

No recursion at all: fill dp[1], dp[2], … left to right. Each new cell reads two finished cells.

loading simulation…
sim

Squeeze the table into two variables

Same answers, but only prev2 and prev1 live in memory. Faded cells are values nobody will read again.

loading simulation…
sim

Grid DP as a 3D landscape

Each tile's height is its path count. Watch the landscape rise toward the goal corner. Orbit with the mouse.

loading simulation…
sim

Min Path Sum: build, then trace back

Each cell takes the cheaper of above/left. At the end, walking back along cheaper neighbours reveals the path.

loading simulation…
sim

0/1 Knapsack table

Row i allows items 0..i−1. Every cell compares "skip" (straight above) with "take" (above, shifted left by the weight).

loading simulation…
sim

LCS table with arrows

↖ = characters match (extend the diagonal). ↑ / ← = drop a character from one string. Follow the arrows back to read the LCS.

loading simulation…
sim

Edit Distance table

Each cell = cheapest of replace (↖), delete (↑), insert (←), or free when the characters match.

loading simulation…
sim

LIS in O(n²)

dp[i] = longest increasing run that ENDS at i. Each i looks back at every smaller earlier number.

loading simulation…
sim

LIS in O(n log n): patience piles

Deal numbers onto piles like solitaire. Number of piles = LIS length. Binary search picks the pile.

loading simulation…

05 · cost

Time & space, explained

The DP ladder (Fibonacci / Climbing Stairs)
TimeExtra spacewhy
Plain recursionO(2ⁿ) (≈ 1.618ⁿ)O(n)Each call spawns two more; the tree has ~φⁿ nodes. Space is the recursion depth.
Memoization (top-down)O(n)O(n)Each of the n+1 states is computed once; later calls are O(1) lookups. Memo + stack are O(n).
Tabulation (bottom-up)O(n)O(n)One loop over n cells, O(1) work each. The table has n+1 slots, no stack.
Space-optimisedO(n)O(1)Each cell reads only the previous two, so two variables replace the table.
Classic DP shapes
TimeTable spaceOptimised spacewhy
1D linear (stairs, robber, decode ways)O(n)O(n)O(1)n states × O(1) transition; only the last 1–2 cells are read.
Coin change / Combination Sum IV (amount A, k values)O(A·k)O(A)O(A)A states, each tries k values. The 1D table is already minimal.
Grid m × n (unique paths, min path, maximal square)O(m·n)O(m·n)O(n)One O(1) step per cell; a row only reads itself and the row above.
Triangle (n rows)O(n²)O(n²)O(n)n(n+1)/2 cells, O(1) each; bottom-up needs only the row below.
0/1 knapsack (n items, capacity W)O(n·W)O(n·W)O(W)n·W cells, O(1) each. Pseudo-polynomial: it grows with the value W, not its digit count.
LISO(n²) or O(n log n)O(n)O(n)n states × O(n) look-back; patience replaces the look-back with a binary search.
Two strings (LCS, edit distance)O(m·n)O(m·n)O(min(m, n))One cell per pair of prefixes; each reads 3 neighbours from this row and the previous one.
Interval DP (burst balloons)O(n³)O(n²)O(n²)n² intervals × O(n) choices of split point. Every interval may be read later, so no row trick.

06 · java

Raw vs Collections Framework

Memo: plain array vs HashMap

javalong[] memojava.util.*
class FibMemoArray {    /** Same idea with a plain array: index = n, 0 = "not computed yet" (fib(k) > 0 for k >= 1). */    static long fib(int n) {        return fib(n, new long[n + 1]);    }     private static long fib(int n, long[] memo) {        if (n <= 1) return n;        if (memo[n] != 0) return memo[n];        return memo[n] = fib(n - 1, memo) + fib(n - 2, memo);    }}
  • When states are small integers (0..n, or i×j), use an array: faster, no boxing, and int[]/long[] start at 0 for free.
  • Use a HashMap when states are sparse, huge, negative, or not integers (e.g. a String key or a pair encoded as i + "," + j).
  • You need a value that means "not computed yet". 0 works only if 0 can't be a real answer; otherwise Arrays.fill(memo, -1) or use Integer[]/Boolean[] where null = unknown.
  • Don't write memo.computeIfAbsent(n, k -> fib(k - 1) + fib(k - 2)) on a HashMap: the recursive call modifies the map inside computeIfAbsent, which throws ConcurrentModificationException (Java 9+). Use get → compute → put.

Bottom-up table, then squeeze it

javalong[] dp tablejava.util.*
class FibTable {    /** Bottom-up: fill the table from the smallest subproblem upward. No recursion at all. */    static long fib(int n) {        if (n <= 1) return n;        long[] dp = new long[n + 1];        dp[0] = 0;        dp[1] = 1;        for (int i = 2; i <= n; i++) {            dp[i] = dp[i - 1] + dp[i - 2];        }        return dp[n];    }}
  • Tabulation has no recursion, so no StackOverflowError on large n — Java's default stack overflows somewhere around 10⁴ frames.
  • Always write the full table first. Only squeeze to variables or rows once it works — it's an easy, safe last step in an interview.
  • long instead of int when values grow fast: fib(47) already overflows int.

Knapsack: 2D table vs one rolling row

java0/1 knapsack, 2Djava.util.*
class Knapsack01 {    /** dp[i][c] = best value using only the first i items with capacity c. */    static int knapsack(int[] wt, int[] val, int cap) {        int n = wt.length;        int[][] dp = new int[n + 1][cap + 1]; // row 0 = no items = value 0        for (int i = 1; i <= n; i++) {            for (int c = 0; c <= cap; c++) {                dp[i][c] = dp[i - 1][c]; // option 1: skip item i                if (wt[i - 1] <= c) {                    // option 2: take item i once, so look at the row ABOVE                    dp[i][c] = Math.max(dp[i][c], dp[i - 1][c - wt[i - 1]] + val[i - 1]);                }            }        }        return dp[n][cap];    }}
  • In the 1D version, dp[c - w] must still be the value from the previous item's row. Going downward guarantees we haven't overwritten it yet.
  • Going upward instead lets an item be counted twice — that's the unbounded knapsack below.

Unbounded knapsack: same code, opposite loop

javaunlimited copies (capacity upward)java.util.*
class KnapsackUnbounded {    /** Unlimited copies: loop capacity UPWARD so dp[c - w] may already contain item i. */    static int knapsack(int[] wt, int[] val, int cap) {        int[] dp = new int[cap + 1];        for (int i = 0; i < wt.length; i++) {            for (int c = wt[i]; c <= cap; c++) {                dp[c] = Math.max(dp[c], dp[c - wt[i]] + val[i]);            }        }        return dp[cap];    }}
  • Coin Change (fewest coins) and Coin Change II (count ways) are both unbounded knapsack: coins can be reused.
  • Outer loop over items + inner loop over capacity counts combinations. Swap the loops and you count ordered sequences — that's Combination Sum IV (LC 377) in this chapter.
Java API you will actually use
calldoescost
new int[n + 1]Table of n+1 zeros (index = state). Off-by-one +1 so dp[n] exists.O(n)
new int[m + 1][n + 1]2D table, row 0 / column 0 = empty prefixO(m·n)
Arrays.fill(dp, -1)Mark every state "not computed" (or fill with a sentinel like amount + 1)O(n)
new Integer[m][n] / new Boolean[n]Memo where null = unknown (when 0 / false are real answers)O(m·n)
map.get(k) / map.put(k, v)HashMap memo for sparse or non-integer statesO(1) avg
Math.max(a, b) / Math.min(a, b)The heart of every optimisation transitionO(1)
Arrays.binarySearch(a, 0, size, x)Index of x, or −(insertionPoint) − 1 if absent — usable for LIS patienceO(log n)
s.substring(i, j)Copies j − i chars (Word Break, Decode Ways) — not freeO(j − i)
Arrays.stream(nums).sum()Total of an int[] (Partition Equal Subset Sum)O(n)

Fastest way to a correct answer from a recursive idea

→ Memoization (top-down)

Add a cache to the recursion you already wrote. Only reachable states get computed.

Large n (deep recursion) or you need to squeeze space

→ Tabulation (bottom-up)

No stack overflow, and an explicit fill order makes row/variable reuse possible.

States are 0..n or (i, j) grids

→ int[] / int[][] / long[]

Direct indexing, no boxing, cache-friendly.

States are sparse, negative or composite (strings, tuples)

→ HashMap<Key, Value>

Arrays would be huge or impossible to index.

0 or false are legitimate answers

→ Integer[] / Boolean[] (null = unknown) or a −1 sentinel

Otherwise you can't tell "computed as 0" from "never computed".

07 · recognise

Spot the pattern

"Count the number of ways…"

Counting DP: dp[state] = sum of ways of the smaller states it comes from

"Minimum / maximum cost, length, profit…" with choices at each step

Optimisation DP: dp[state] = best over choices (min/max)

"Is it possible to…" (reach, split, partition, form)

Feasibility DP with booleans: dp[state] = OR over choices

Your recursion has parameters that repeat (draw the tree — same arguments appear twice)

Overlapping subproblems → memoize

Choose items with a weight/budget limit; each item once

0/1 knapsack (capacity loop downward in 1D)

Unlimited supply of each item (coins, ribbon pieces)

Unbounded knapsack (capacity loop upward)

"How many sequences add up to target" and the order matters

Unbounded knapsack with the target loop OUTSIDE (Combination Sum IV)

Two strings: compare, align, convert, common subsequence

2D DP on prefixes dp[i][j]

Grid, move only right/down (or down a triangle)

Grid DP: dp[r][c] from above and left; triangles go bottom-up

Largest square of 1s in a binary grid

dp[r][c] = 1 + min(up, left, up-left): the square ending at this corner

Subsequence (not subarray!) that increases / is the longest

LIS: O(n²) DP or O(n log n) patience

Merge / burst / split a range and the order matters

Interval DP: dp[i][j], short intervals first, try every split point k

n ≤ ~20 and you need subsets

Probably backtracking or bitmask DP, not a plain table

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Not writing the meaning of dp[i] in one sentence before coding. Most wrong DP code comes from a fuzzy state.
  • Off-by-one between dp[i] = "first i characters" and s.charAt(i - 1). Pick one convention and say it out loud.
  • Using 0 as "not computed" when 0 is a valid answer — the memo never hits (slow) or returns wrong values.
  • Filling the table in the wrong order: reading dp[i+1] before it's computed silently uses 0.
  • Integer.MAX_VALUE as infinity, then + 1 → overflows to a negative number and becomes the "minimum". Use amount + 1 or check before adding.
  • 1D knapsack with the capacity loop in the wrong direction: upward = items reused, downward = each once.
  • Coin Change II with loops swapped (amount outside, coins inside) counts 1+2 and 2+1 as different — that's Combination Sum IV's answer, not 518's.
  • Greedy instead of DP: coins {1, 3, 4} for amount 6 — greedy gives 4+1+1 (3 coins), DP finds 3+3 (2 coins).
  • Forgetting that recursion depth itself costs O(depth) space, and that Java may throw StackOverflowError for depth ≳ 10⁴.

tricks

  • Always start from the brute-force recursion. Say "this recomputes the same states — let me cache them". That's the interviewer's favourite path.
  • Write the 5 steps as comments before any code: state, transition, base case, order, answer.
  • Chalo, dry-run karte hain: fill the table for a tiny input by hand (3–4 cells). If your hand-made table is right, your code will be.
  • Draw the dependency arrows: if dp[i][j] reads up, left and up-left, the fill order is rows top→bottom, columns left→right.
  • After it works, ask "which cells do I still read?" — that tells you if you can drop to two rows or two variables.
  • Name things in English: ways, best, canMake, fewest. dp alone is fine only if you've said what it means.
  • For "reconstruct the answer" follow-ups, keep the full table and walk back from the answer cell (like the arrows in the LCS sim).

09 · why & how

Questions you might be too shy to ask

How do I know a problem is DP and not greedy or backtracking?

Ask two questions. 1) Do I make a sequence of choices, and is the best answer built from best answers to smaller versions? 2) Does a brute-force recursion hit the same arguments more than once? If both are yes, it's DP. If a simple local rule always works (and you can argue why), it's greedy. If you must list all solutions, not just count or optimise them, it's backtracking.

The memoized fib still calls itself twice. How did it become O(n)?

Each state is fully computed only once. The first fib(5) does the real work and writes it down. Every later fib(5) call finds the answer in the memo and returns in O(1), without going deeper. So at most n + 1 states do real work, each making 2 calls: about 2n calls in total, O(n).

I freeze when I can't see the recurrence. What do I do?

Don't hunt for the formula first. Write the slow recursion that tries every choice — you can always do that. Then circle its changing parameters (that's the state), draw its calls for a tiny input to see the repeats, and add a memo. The table version comes after, almost mechanically. Bas itna yaad rakho: brute force first, cache second, table third.

Memoization or tabulation — which should I write in an interview?

Whichever you can write correctly first. Memoization is usually easier because it's your recursion plus 3 lines. Then mention tabulation removes recursion (no stack overflow) and enables space optimisation. Many interviewers then ask you to convert — the 5-step recipe makes that mechanical.

How do I come up with the state?

Look at your brute-force recursion: its changing parameters are the state. rob(i) → dp[i]. lcs(i, j) → dp[i][j]. knap(i, capacity) → dp[i][c]. If you need more information to decide the next move (like "the previous element" in LIS), it goes into the state too.

Why is the table new int[n + 1], and what value goes in dp[0]?

When dp[i] means "the first i items", i runs from 0 to n, which is n + 1 slots, and the answer sits in dp[n]. dp[0] is the empty case and it's your base case. Ask "what is the answer for nothing?": ways to climb 0 stairs or make amount 0 → 1 (do nothing, one way). Fewest coins for 0 → 0. Can the empty string be broken into words → true.

Why is knapsack O(n·W) called "pseudo-polynomial"?

W is a number, and the table has W+1 columns. If W = 10⁹, the table is impossibly big even though W fits in 4 bytes. Running time is polynomial in the value of W, but exponential in the number of bits used to write W. That's why 0/1 knapsack is still NP-hard in general.

What is the difference between subarray and subsequence?

A subarray (or substring) is contiguous: [2, 3, 4] inside [1, 2, 3, 4, 5]. A subsequence keeps order but may skip elements: [1, 3, 5]. Subarray problems often fall to sliding window or Kadane; subsequence problems usually need DP like LIS or LCS.

My memoized solution gets StackOverflowError. Is it wrong?

Probably not — the recursion is just too deep for Java's default thread stack. How deep is too deep depends on frame size and settings — think "a few thousand to tens of thousands of calls", not an exact number. Convert it to tabulation: same states and transition, filled with loops instead of calls.

How do I know the fill order for tabulation?

Look at what the transition reads. If dp[i] reads dp[i−1], go i upward. If dp[i][j] reads dp[i+1][j−1] (palindromes), go by interval length, short to long. The rule: every cell you read must already be filled.

Why does LIS return the biggest dp[i], not just dp[n − 1]?

dp[i] is the longest run that ends at i. The best run can end anywhere. In [1, 2, 3, 0], dp[3] is 1 (just the 0), but the answer is 3 (1, 2, 3, ending at index 2). So keep a running best over all i. When dp[i] means "the first i items" instead (House Robber), dp[n] already is the answer.

In Edit Distance, why does ↑ mean delete and ← mean insert?

Read the cell you came from. dp[i − 1][j] (↑): the first i − 1 chars of word1 already became word2's first j, so word1's i-th char is extra: delete it. dp[i][j − 1] (←): the first i chars of word1 already became word2's first j − 1, so insert word2's j-th char. dp[i − 1][j − 1] (↖): replace one char with the other. Each costs 1.

How do I return the actual items/path, not just the number?

Keep the full table, then walk backwards from the answer cell asking "which choice produced this value?" In knapsack: if dp[i][c] ≠ dp[i−1][c], item i was taken. In LCS: diagonal moves are kept characters. That's why space-optimised versions often can't reconstruct.

Is DP always faster than greedy?

No — greedy is usually faster (often O(n) or O(n log n)) when it is correct. DP is the safe tool when greedy has a counter-example. Coin change with coins {1, 3, 4}: greedy picks 4+1+1 for 6, DP finds 3+3.

10 · practice

Interview problems

Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.

  1. #70
    Climbing Stairsmust-do
    EasyAdobeInfosysBloombergAmazonMicrosoft+1
  2. #746
    Min Cost Climbing Stairs
    EasyAmazonGoogleBloombergMicrosoft
  3. #198
    House Robbermust-do
    MediumInfosysAmazonByteDance / TikTokAirbnbWalmart+1
  4. #213
    House Robber IImust-do
    MediumMicrosoftAmazonGoogle
  5. #91
    Decode Waysmust-do
    MediumMicrosoftMetaAmazonGoogle
  6. #152
    Maximum Product Subarraymust-do
    MediumLinkedInAdobeAmazonGoogleMicrosoft
  7. #139
    Word Breakmust-do
    MediumWalmartIntuitUberAmazonBloomberg+1
  8. #322
    Coin Changemust-do
    MediumPayPalIntuitByteDance / TikTokWalmartAmazon+1
  9. #518
    Coin Change II
    MediumAmazonGoogleMicrosoftBloomberg
  10. #377
    Combination Sum IV
    MediumGoogleMetaAmazonBloombergMicrosoft
  11. #416
    Partition Equal Subset Summust-do
    MediumAmazonGoogleMicrosoftMeta
  12. #62
    Unique Pathsmust-do
    MediumAmazonBloombergMetaGoogleGoldman Sachs
  13. #63
    Unique Paths II
    MediumBloombergAmazonGoogleMicrosoftGoldman Sachs
  14. #64
    Minimum Path Sum
    MediumGoldman SachsMicrosoftAmazonGoogle
  15. #120
    Triangle
    MediumBloombergAmazonMicrosoftGoogleGoldman Sachs
  16. #221
    Maximal Square
    MediumPayPalByteDance / TikTokAirbnbGoogleApple+1
  17. #300
    Longest Increasing Subsequencemust-do
    MediumSamsungByteDance / TikTokPayPalWalmartMicrosoft+1
  18. #1143
    Longest Common Subsequencemust-do
    MediumMicrosoftGoogleAmazonMeta
  19. #72
    Edit Distancemust-do
    MediumAmazonBloombergMicrosoftGoogle
  20. #5
    Longest Palindromic Substringmust-do
    MediumTCSMicrosoftBloombergAmazonByteDance / TikTok+1
  21. #312
    Burst Balloons
    HardSamsungGoogleAmazon

Also practise this here

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #509
    Fibonacci Number
    Easyin Recursion

    The smallest DP there is — memo or two variables. The warm-up for Climbing Stairs.

  2. #53
    Maximum Subarray
    Mediumin Arrays & Strings

    Kadane's algorithm is 1D DP: best sum ending here = max(x, best ending before + x).

  3. #338
    Counting Bits
    Easyin Bits

    dp[i] = dp[i >> 1] + (i & 1): a one-line DP over numbers instead of indices.

  4. #329
    Longest Increasing Path in a Matrix
    Hardin Topo Sort

    DP on a DAG: memoize the longest path from each cell — LIS in two dimensions.

  5. #787
    Cheapest Flights Within K Stops
    Mediumin Shortest Paths

    Bellman–Ford is DP over "paths that use at most k edges" — one table row per extra edge.

11 · revise

The whole chapter on one screen

10 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    DP = recursion + a notebook. Solve each small question once, store it, and build bigger answers from stored ones.

  2. use it when

    "Count the ways", "min/max cost", "is it possible" — with a choice at each step and a brute-force recursion that repeats the same arguments.

  3. template

    State (what dp[i] means) → transition → base cases → fill order → answer cell. Write these 5 lines as comments first.

  4. cost

    Time = states × work per state. Stairs: n × O(1). LCS: m·n × O(1). LIS: n × O(n). Knapsack: n·W — pseudo-polynomial.

  5. template

    Memo: int[] memo filled with −1, check → compute → store. Table: new int[n + 1], loop up from the base cases.

  6. pro tip

    Only the last 1–2 cells or rows are read? Keep prev2/prev1 or prev/cur rows: O(n) → O(1), O(m·n) → O(n).

  7. trap

    0/1 knapsack in 1D: loop capacity DOWN. Going up lets an item be reused — that's unbounded knapsack.

  8. trap

    Coins outside → combinations (LC 518). Target outside → orderings (LC 377). Swap the loops and you change the answer.

  9. trap

    Integer.MAX_VALUE + 1 wraps negative: use amount + 1 as infinity. Use −1 (not 0) for "not computed" when 0 is a real answer.

  10. pro tip

    Stuck? Write the brute-force recursion first. Its changing parameters ARE the DP state.

12 · recall

Flip, remember, repeat

card 1 / 15
recall

Which two properties make a problem a DP problem?

tap or Space to flip ↻

Question 1: Which two properties make a problem a DP problem?

13 · check yourself

Quick quiz

  1. Which two properties make a problem a good fit for DP?

  2. Memoized fib(n) makes how many calls?

  3. In the 1D 0/1 knapsack, why does the capacity loop go downward?

  4. Coins {1, 3, 4}, amount 6. Greedy (largest coin first) uses how many coins, and what's optimal?

  5. dp[t] += dp[t − x], with the target loop OUTSIDE and the numbers loop inside. What does it count?

  6. LCS of "abcde" and "ace": text1[i−1] == text2[j−1]. What is dp[i][j]?

  7. Unique Paths II: the START cell is a rock. What is the answer?

  8. What does tails[k] mean in the O(n log n) LIS?

  9. dp[i][j] for a palindrome table reads dp[i+1][j−1]. Which fill order is correct?

  10. Your memoized Java solution throws StackOverflowError at n = 10⁵. Best fix?