Dynamic Programming

Use when: Overlapping subproblems, optimal substructure - “how many ways”, “min/max cost”, “can we reach”.

Complexity: Varies - typically O(N) to O(N^2) or O(N*M)

1D DP - Linear

# Example: climb stairs / coin change
def dp_1d(n):
    dp = [0] * (n + 1)
    dp[0] = 1  # base case
 
    for i in range(1, n + 1):
        for choice in choices:
            if i - choice >= 0:
                dp[i] += dp[i - choice]  # or min/max
 
    return dp[n]

2D DP - Grid

# Example: unique paths, min path sum
def dp_grid(grid):
    rows, cols = len(grid), len(grid[0])
    dp = [[0] * cols for _ in range(rows)]
    dp[0][0] = grid[0][0]
 
    for r in range(rows):
        for c in range(cols):
            if r == 0 and c == 0:
                continue
            from_top  = dp[r-1][c] if r > 0 else float('inf')
            from_left = dp[r][c-1] if c > 0 else float('inf')
            dp[r][c] = grid[r][c] + min(from_top, from_left)
 
    return dp[rows-1][cols-1]

2D DP - Two Sequences

# Example: LCS, edit distance
def dp_two_seq(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
 
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            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])
 
    return dp[m][n]

Knapsack (0/1)

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]  # skip item
            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]

Signals in a Problem

  • “How many ways to…”
  • “Minimum/maximum cost to…”
  • “Can you reach / partition / form…”
  • Choices at each step with overlapping subproblems