Dynamic Programming
DP problems feel hard until you learn to spot the pattern. Most LeetCode DP problems fall into 5–6 archetypes.
The Core Idea
DP = recursion + memoization (or iterative tabulation).
If a problem has overlapping subproblems and optimal substructure, DP applies.
Step-by-step approach:
- Define what
dp[i](ordp[i][j]) means in plain English - Write the recurrence relation
- Identify base cases
- Decide: top-down (memo) or bottom-up (table)
Pattern 1: Linear DP — "At each step, make a choice"
Climbing Stairs (Fibonacci pattern)
# dp[i] = number of ways to reach step i
def climb_stairs(n):
if n <= 2: return n
prev, curr = 1, 2
for _ in range(3, n + 1):
prev, curr = curr, prev + curr
return curr
House Robber
# dp[i] = max money robbing up to house i
def rob(nums):
prev, curr = 0, 0
for n in nums:
prev, curr = curr, max(curr, prev + n)
return curr
Pattern 2: 0/1 Knapsack — "Include or exclude"
# dp[i][w] = max value using first i items with capacity w
def knapsack(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(capacity + 1):
dp[i][w] = dp[i-1][w] # exclude
if weights[i-1] <= w:
dp[i][w] = max(dp[i][w], dp[i-1][w - weights[i-1]] + values[i-1])
return dp[n][capacity]
Related problems: Partition Equal Subset Sum [416], Target Sum [494], Last Stone Weight II [1049]
Pattern 3: Unbounded Knapsack — "Use item multiple times"
Coin Change
# dp[amount] = min coins to make amount
def coin_change(coins, amount):
dp = [float('inf')] * (amount + 1)
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] if dp[amount] != float('inf') else -1
Pattern 4: 2D DP — "Two sequences / grid"
Longest Common Subsequence
# dp[i][j] = LCS length of text1[:i] and text2[:j]
def lcs(text1, text2):
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
Edit Distance (Levenshtein)
def min_distance(word1, word2):
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1): dp[i][0] = i
for j in range(n + 1): dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
return dp[m][n]
Pattern 5: Interval DP — "Merge / split ranges"
Matrix Chain Multiplication / Burst Balloons pattern
# Burst Balloons [312]
def max_coins(nums):
nums = [1] + nums + [1]
n = len(nums)
dp = [[0] * n for _ in range(n)]
for length in range(2, n):
for left in range(0, n - length):
right = left + length
for k in range(left + 1, right):
dp[left][right] = max(
dp[left][right],
nums[left] * nums[k] * nums[right] + dp[left][k] + dp[k][right]
)
return dp[0][n - 1]
Pattern 6: State Machine DP — "Finite states"
Best Time to Buy and Sell Stock with Cooldown
def max_profit(prices):
held, sold, rest = -float('inf'), 0, 0
for p in prices:
held, sold, rest = max(held, rest - p), held + p, max(rest, sold)
return max(sold, rest)
DP Decision Tree
Does the problem ask for: max/min/count/true-false? → Likely DP
Is there a "choices at each step"? → Linear or knapsack DP
Two strings / sequences? → 2D DP (LCS, edit distance)
Grid / matrix? → 2D DP (unique paths, dungeon)
Subarrays / subsets? → Knapsack or prefix sum
Practice Problems (LeetCode)
Linear DP
- [70] Climbing Stairs
- [198] House Robber
- [300] Longest Increasing Subsequence
- [152] Maximum Product Subarray
Knapsack
- [416] Partition Equal Subset Sum
- [322] Coin Change
- [518] Coin Change II
2D DP
- [1143] Longest Common Subsequence
- [72] Edit Distance
- [62] Unique Paths
- [64] Minimum Path Sum
Hard
- [312] Burst Balloons
- [10] Regular Expression Matching
- [44] Wildcard Matching