Skip to main content
Dynamic Programming Pattern

What is Dynamic Programming?

Dynamic Programming solves complex problems by breaking them into overlapping subproblems and storing results to avoid redundant computation. It transforms exponential solutions into polynomial ones.
Quick Recognition: If you see “optimal” (min/max), “number of ways”, or “can we achieve” combined with choices at each step, think DP!

Pattern Recognition Checklist

Use DP When

  • Problem has optimal substructure
  • Same subproblems solved repeatedly
  • Need to find min/max/count
  • Choices at each step affect result
  • Can break into smaller similar problems

Don't Use When

  • Greedy choice works (no need to try all)
  • No overlapping subproblems
  • Need all solutions (use Backtracking)
  • Simple iteration is sufficient

When to Use

Optimal Substructure

Optimal solution contains optimal solutions to subproblems

Overlapping Subproblems

Same subproblems are solved multiple times

Counting Problems

Number of ways to achieve something

Optimization

Minimize/maximize under constraints

The IDEAL Framework

1

Identify

Recognize it’s a DP problem (optimal/count + choices + constraints)
2

Define

Define what dp[i] or dp[i][j] represents
3

Express

Write the recurrence relation (transition equation)
4

Analyze

Determine base cases and computation order
5

Look back

Optimize space if possible

Pattern Variations

1. 1D DP (Linear)

def climb_stairs(n):
    """Number of ways to climb n stairs (1 or 2 steps)"""
    if n <= 2:
        return n
    
    # dp[i] = ways to reach step i
    dp = [0] * (n + 1)
    dp[1] = 1
    dp[2] = 2
    
    for i in range(3, n + 1):
        dp[i] = dp[i-1] + dp[i-2]  # Come from i-1 or i-2
    
    return dp[n]

# Space optimized
def climb_stairs_optimized(n):
    if n <= 2:
        return n
    
    prev2, prev1 = 1, 2
    for i in range(3, n + 1):
        prev2, prev1 = prev1, prev2 + prev1
    
    return prev1

2. 2D DP (Grid/Sequence)

def unique_paths(m, n):
    """Count paths from top-left to bottom-right (right/down only)"""
    # dp[i][j] = paths to reach cell (i, j)
    dp = [[1] * n for _ in range(m)]
    
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = dp[i-1][j] + dp[i][j-1]
    
    return dp[m-1][n-1]

# Space optimized to O(n)
def unique_paths_optimized(m, n):
    dp = [1] * n
    
    for i in range(1, m):
        for j in range(1, n):
            dp[j] += dp[j-1]
    
    return dp[n-1]

3. Knapsack Pattern

def knapsack_01(weights, values, capacity):
    """Maximum value within capacity (each item once)"""
    n = len(weights)
    # dp[i][w] = max value using first i items with capacity w
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for w in range(capacity + 1):
            # Don't take item i
            dp[i][w] = dp[i-1][w]
            
            # Take item i if it fits
            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]

# Space optimized
def knapsack_01_optimized(weights, values, capacity):
    dp = [0] * (capacity + 1)
    
    for i in range(len(weights)):
        # Traverse backwards to avoid using same item twice
        for w in range(capacity, weights[i] - 1, -1):
            dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
    
    return dp[capacity]

4. LCS Pattern (Two Strings)

def longest_common_subsequence(text1, text2):
    """Find LCS length of two strings"""
    m, n = len(text1), len(text2)
    # dp[i][j] = LCS of text1[:i] and text2[:j]
    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]

5. House Robber (Cannot Take Adjacent)

def house_robber(nums):
    """Maximum sum without taking adjacent elements"""
    if not nums:
        return 0
    if len(nums) == 1:
        return nums[0]
    
    # dp[i] = max money robbing houses 0..i
    # Either rob house i (dp[i-2] + nums[i]) or skip it (dp[i-1])
    dp = [0] * len(nums)
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    
    for i in range(2, len(nums)):
        dp[i] = max(dp[i-1], dp[i-2] + nums[i])
    
    return dp[-1]

# Space optimized
def house_robber_optimized(nums):
    prev2, prev1 = 0, 0
    
    for num in nums:
        prev2, prev1 = prev1, max(prev1, prev2 + num)
    
    return prev1

6. Coin Change

def coin_change(coins, amount):
    """Minimum coins to make amount"""
    # dp[i] = min coins to make amount i
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    
    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i and dp[i - coin] != float('inf'):
                dp[i] = min(dp[i], dp[i - coin] + 1)
    
    return dp[amount] if dp[amount] != float('inf') else -1

Classic Problems

Pattern: 1D DP (Fibonacci-like)State: dp[i] = ways to reach step i
Pattern: 1D DP with O(n^2) or binary search O(n log n)State: dp[i] = LIS ending at index i
Pattern: 2D DP on two stringsState: dp[i][j] = min operations to convert s1[:i] to s2[:j]
Pattern: 0/1 Knapsack variantState: dp[i][sum] = can we make sum using first i elements?

Common Mistakes

Avoid These Pitfalls:
  1. Wrong state definition: Be precise about what dp[i] represents
  2. Missing base cases: Initialize dp[0] or dp[0][0] correctly
  3. Wrong iteration order: Depends on dependencies in recurrence
  4. Integer overflow: Use long or modular arithmetic when needed

Debugging Checklist

When your DP solution fails:
1

Check State Definition

Is dp[i] clearly defined? Can you explain what it represents?
2

Check Base Cases

Have you initialized all necessary base cases correctly?
3

Check Recurrence

Does your transition cover all cases? Any edge cases missed?
4

Check Iteration Order

Are you computing dp[i] only after dp[i-1], dp[i-2], etc.?
5

Check Return Value

Are you returning the right cell? dp[n] vs dp[n-1]?

DP Pattern Categories

CategoryExamplesState Definition
Linear DPClimbing Stairs, House Robberdp[i] = answer for first i elements
Grid DPUnique Paths, Min Path Sumdp[i][j] = answer reaching (i,j)
String DPLCS, Edit Distancedp[i][j] = answer for s1[:i], s2[:j]
Knapsack0/1 Knapsack, Coin Changedp[i][w] = answer using i items, capacity w
Interval DPMatrix Chain, Burst Balloonsdp[i][j] = answer for interval [i,j]
Tree DPHouse Robber IIIdp[node] = answer for subtree at node

Complexity Quick Reference

Problem TypeTimeSpaceOptimization
1D DPO(n)O(n) → O(1)Keep only last 2 values
2D Grid DPO(mn)O(mn) → O(n)Keep only previous row
String DP (LCS)O(mn)O(mn) → O(n)Keep only previous row
KnapsackO(nW)O(nW) → O(W)Single row iteration
Interval DPO(n³)O(n²)Usually not optimizable

Interview Problems by Company

ProblemCompanyDP Type
Climbing StairsAll1D Linear
Min Cost Climbing StairsAmazon1D Linear
Maximum SubarrayAll1D (Kadane’s)
Best Time to Buy StockAmazon1D State Machine
House RobberGoogle, Meta1D Linear

Interview Tips

Script for interviews:
  1. “I notice this is asking for [optimal/count], which suggests DP.”
  2. “Let me define the state: dp[i] represents…”
  3. “The recurrence is: dp[i] = … because…”
  4. “Base case is dp[0] = … because…”
  5. “Time is O(…) and space is O(…), which I can optimize to…”
Interviewer SaysYou Should Think
”Find minimum/maximum”Optimization DP
”Count the number of ways”Counting DP
”Can you achieve X?”Boolean DP (true/false)
“Optimize your recursion”Add memoization
”Reduce space complexity”Rolling array technique
Top-Down (Memoization):
  • Easier to write (natural recursion)
  • Only computes needed subproblems
  • Can have stack overflow for large inputs
Bottom-Up (Tabulation):
  • Usually faster (no recursion overhead)
  • Easier to optimize space
  • Must compute all subproblems
Recommendation: Start with top-down, convert to bottom-up if needed.

Practice Problems

ProblemDifficultyLink
Climbing StairsEasyLeetCode 70
House RobberMediumLeetCode 198
Coin ChangeMediumLeetCode 322
Longest Common SubsequenceMediumLeetCode 1143
Edit DistanceMediumLeetCode 72

Practice Roadmap

1

Week 1: 1D DP

  • Solve: Climbing Stairs, House Robber, Max Subarray
  • Focus: State definition and transitions
2

Week 2: 2D Grid DP

  • Solve: Unique Paths, Min Path Sum, Triangle
  • Focus: Grid traversal patterns
3

Week 3: String DP

  • Solve: LCS, Edit Distance, Palindrome Substring
  • Focus: Two-string state definition
4

Week 4: Advanced

  • Solve: Coin Change, Knapsack, Word Break
  • Focus: Recognizing DP variants
Interview Tip: Start with brute force recursion, add memoization, then convert to bottom-up DP, finally optimize space.