Classic DP Patterns
Seven shapes cover the overwhelming majority of DP interview problems. Once youβve internalized these, most βnewβ problems are just one of these in a costume.
For each pattern weβll give: the canonical problem, the state, the transition, the code, and a list of other problems that wear the same skin.
Pattern 1 β Linear (1D) DP
The simplest shape. State is a single index i. dp[i] depends on a constant number of earlier cells like dp[i - 1], dp[i - 2], or dp[i - k] for fixed k.
Canonical: House Robber
You canβt rob two adjacent houses. Maximize your loot from a row of houses with values
nums[i].
State: dp[i] = max loot from the prefix nums[0..i].
Transition: at house i, either skip it (dp[i - 1]) or rob it and the best from before its neighbor (nums[i] + dp[i - 2]).
Base: dp[0] = nums[0], dp[1] = max(nums[0], nums[1]).
Space-optimizable to O(1) β just keep prev1 and prev2. Classic linear DP signature.
Same skin
- Climbing Stairs β
dp[i] = dp[i-1] + dp[i-2]. - Min Cost Climbing Stairs β
dp[i] = cost[i] + min(dp[i-1], dp[i-2]). - Maximum Subarray (Kadane) β
dp[i] = max(nums[i], nums[i] + dp[i-1]). - Decode Ways β
dp[i] = dp[i-1] (if single-digit valid) + dp[i-2] (if two-digit valid).
Pattern 2 β Grid (2D) DP
State is a pair (i, j) β usually a position in a grid or two parallel strings. dp[i][j] depends on neighbors like dp[i-1][j], dp[i][j-1], dp[i-1][j-1].
Canonical: Unique Paths
Count distinct paths from
(0, 0)to(m-1, n-1)in a grid, moving only right or down.
State: dp[i][j] = number of paths to cell (i, j).
Transition: dp[i][j] = dp[i - 1][j] + dp[i][j - 1].
Base: the entire first row and first column are 1 (only one way to reach them).
Space-optimizable to O(n) β only the previous row is needed.
Same skin
- Unique Paths II (with obstacles) β same transition, plus
dp[i][j] = 0on blocked cells. - Minimum Path Sum β
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]). - Maximal Square β
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])ifgrid[i][j] == 1. - Triangle min path sum β same shape on a triangle grid.
Pattern 3 β 0/1 Knapsack
You have items, each with a weight and a value. You have a capacity. Pick a subset that maximizes value under the capacity constraint. Each item can be picked at most once.
State: dp[i][w] = max value using the first i items with capacity w.
Transition: at item i, either skip it (dp[i - 1][w]) or take it (dp[i - 1][w - weight[i]] + value[i], if it fits).
Base: dp[0][w] = 0 (no items β no value, any capacity).
Time: O(n Γ W). Space: O(n Γ W), optimizable to O(W) by iterating capacity in reverse (so we donβt double-count an item taken in the current row):
dp = [0] * (W + 1)
for i in range(n):
for w in range(W, weight[i] - 1, -1): # reverse β critical
dp[w] = max(dp[w], dp[w - weight[i]] + value[i])The reverse loop is the most common knapsack interview gotcha. Iterating forward turns 0/1 knapsack into unbounded knapsack β a different problem.
Same skin
- Partition Equal Subset Sum β knapsack where capacity =
sum / 2and values = weights; ask βcan we hit exactly that capacity?β - Target Sum β assign
+or-to each number to hit a target; reduces to a subset-sum DP. - Last Stone Weight II β minimize
|sum_A - sum_B|; reduces to subset sum. - Ones and Zeroes β knapsack with two capacities (count of 0s and count of 1s).
Pattern 4 β Unbounded Knapsack
Same as 0/1 knapsack, but each item can be picked infinitely many times. The state is still dp[i][w], but in the recurrence the βtakeβ branch comes from dp[i][w - weight[i]] instead of dp[i - 1][...] β because after taking item i, youβre allowed to take it again.
Canonical: Coin Change
Given coins of denominations
coins[]and an amountn, whatβs the minimum number of coins to maken? Return -1 if impossible.
State: dp[w] = min coins for amount w.
Transition: dp[w] = min(dp[w - c] + 1) over all coins c <= w.
Base: dp[0] = 0.
Time: O(n Γ amount). Space: O(amount) already, no further optimization needed.
Same skin
- Coin Change II (count of ways, not min) β iterate coins outside and amount inside for βcombinationsβ; flip for βpermutations.β
- Perfect Squares β coins are
[1, 4, 9, 16, ...]; find min coins to sum ton. - Word Break β βcoinsβ are dictionary words, βamountβ is the string; can we tile it?
- Combination Sum IV β count tilings allowing different orderings.
Coin Change II vs Coin Change is the most common DP-loop-order trap. To count combinations (unordered), iterate coins in the outer loop and amount in the inner. To count permutations (ordered), do the reverse. Same recurrence, different loop order β different problem.
Pattern 5 β Longest Increasing Subsequence (LIS)
A subsequence is a sequence you get by deleting some elements without changing the order of the rest. LIS = the longest such subsequence thatβs strictly increasing.
State: dp[i] = length of the LIS that ends at index i.
Transition: dp[i] = 1 + max(dp[j]) for all j < i with nums[j] < nums[i]. If no such j exists, dp[i] = 1.
Answer: max(dp[i]) over all i (the LIS doesnβt have to end at the last element).
Time: O(nΒ²). Space: O(n).
Thereβs an O(n log n) LIS using a patience-sorting trick β maintain a list of βtail of best subsequence of each lengthβ and binary-search the insertion point. We cover it in the practice problem. Itβs worth knowing if the interviewer says βnow do it faster.β
Same skin
- Number of LIS β track count of LIS-of-max-length in addition to the length.
- Russian Doll Envelopes β sort by width, then LIS on heights.
- Longest Bitonic Subsequence β LIS from the left + LIS from the right.
- Box Stacking β sort boxes by base, LIS by height with stackability check.
Pattern 6 β Longest Common Subsequence (LCS)
Two strings. Find the longest sequence of characters appearing in both, in order, but not necessarily contiguously.
State: dp[i][j] = length of LCS of s1[0..i-1] and s2[0..j-1].
Transition: if s1[i - 1] == s2[j - 1], dp[i][j] = dp[i - 1][j - 1] + 1. Else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]).
Base: dp[0][j] = dp[i][0] = 0.
Time: O(n Γ m). Space: O(n Γ m), optimizable to O(m) by rolling on rows.
Same skin
- Longest Common Substring β same but
dp[i][j]resets to 0 on mismatch. The answer ismax(dp[i][j])everywhere, notdp[n][m]. - Shortest Common Supersequence β
n + m β LCS. - Distinct Subsequences β count, not length:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]on match, elsedp[i-1][j]. - Longest Palindromic Subsequence β LCS of
swithreverse(s).
Pattern 7 β Edit Distance
Two strings. Find the minimum number of single-character insertions, deletions, or replacements to turn s1 into s2. The Swiss army knife of string DP.
State: dp[i][j] = min edits to turn s1[0..i-1] into s2[0..j-1].
Transition: if s1[i - 1] == s2[j - 1], dp[i][j] = dp[i - 1][j - 1] (no edit needed). Otherwise, take 1 plus the minimum of:
dp[i - 1][j - 1]β replacedp[i - 1][j]β delete froms1dp[i][j - 1]β insert intos1(equivalent to advancing ins2)
Base: dp[0][j] = j (insert j chars), dp[i][0] = i (delete i chars).
Time: O(n Γ m). Space: O(n Γ m), optimizable to O(m).
Same skin
- One Edit Distance β early exit when count exceeds 1.
- Delete Operation for Two Strings β
n + m β 2 Γ LCS. - Minimum ASCII Delete Sum β same shape, but
+= ascii(char)instead of+= 1. - Wildcard Matching / Regex Matching β same skin,
*and?extend the transition table.
The classification cheat sheet
| Pattern | State signature | Hallmark recurrence ingredient | Time |
|---|---|---|---|
| 1D linear | dp[i] | dp[i-1], dp[i-2] | O(n) |
| 2D grid | dp[i][j] | dp[i-1][j], dp[i][j-1] | O(n Γ m) |
| 0/1 knapsack | dp[i][w] | skip vs take (use dp[i-1][...]) | O(n Γ W) |
| Unbounded knapsack | dp[w] | dp[w - coin] (reuse current row) | O(n Γ W) |
| LIS | dp[i] | max(dp[j]+1) over j < i | O(nΒ²) or O(n log n) |
| LCS | dp[i][j] | match: dp[i-1][j-1]+1; else max | O(n Γ m) |
| Edit distance | dp[i][j] | match: dp[i-1][j-1]; else 1 + min of 3 | O(n Γ m) |
If a problem matches one of these state signatures and one of these recurrence ingredients, youβre already 80% done.
Quick check
Next: advanced DP β space optimization, bitmask DP, interval DP, and DP on trees.