name: Dynamic Programming
slug: dynamic-programming
difficulty_level: 4
pattern_type: technique
display_order: 11

description: >
  Break problems into overlapping subproblems, storing results to avoid
  recomputation. This transforms exponential time complexity into polynomial
  by trading space for time.

when_to_use: |
  - Optimization problems (min/max)
  - Counting problems
  - Problems with optimal substructure
  - Sequence alignment
  - Knapsack-type problems

metaphor: |
  Imagine building with LEGO bricks. Instead of reconstructing the same base
  structure every time you try a new top, you save your work. Each completed
  substructure becomes a building block for larger structures.

  Another analogy: calculating Fibonacci numbers. To find fib(5), you need
  fib(4) and fib(3). But fib(4) also needs fib(3). Rather than recalculating
  fib(3) twice, save it the first time and reuse it.

core_concept: |
  Dynamic programming requires two properties:

  1. **Optimal substructure**: The optimal solution contains optimal solutions
     to its subproblems.

  2. **Overlapping subproblems**: The same subproblems are solved multiple
     times in a naive recursive approach.

  The key insight is identifying the **state**—what information do you need
  to solve a subproblem? And the **transition**—how do you combine smaller
  subproblems into larger ones?

  Two implementation approaches:
  - **Top-down (memoization)**: Recursive with caching
  - **Bottom-up (tabulation)**: Iterative, filling a table from base cases

visualization: |
  **Example: Fibonacci with memoization**

  ```
  Without memoization (exponential calls):
                    fib(5)
                   /      \
              fib(4)      fib(3)
             /    \       /    \
         fib(3)  fib(2) fib(2) fib(1)
         /   \
     fib(2) fib(1)
     ...

  With memoization:
  fib(5) → fib(4) → fib(3) → fib(2) → fib(1)
           ↓          ↓
           use cached use cached
           fib(3)     fib(2)
  ```

  **Example: Coin Change (minimum coins for amount 11)**

  ```
  Coins: [1, 5, 6]   Amount: 11

  dp[0] = 0 (base case: 0 coins for amount 0)

  dp[1] = dp[0] + 1 = 1  (use coin 1)
  dp[5] = min(dp[4]+1, dp[0]+1) = 1  (use coin 5)
  dp[6] = min(dp[5]+1, dp[0]+1) = 1  (use coin 6)

  dp[11] = min(dp[10]+1, dp[6]+1, dp[5]+1)
         = min(?, 2, 3)
         = 2  (6 + 5)
  ```

code_template: |
  # Top-down (memoization)
  from functools import lru_cache

  def solve_top_down(n: int) -> int:
      @lru_cache(maxsize=None)
      def dp(state):
          # Base case
          if base_condition(state):
              return base_value

          # Recursive case with memoization
          result = initial_value
          for choice in choices(state):
              subproblem = dp(next_state(state, choice))
              result = combine(result, subproblem)

          return result

      return dp(initial_state(n))


  # Bottom-up (tabulation)
  def solve_bottom_up(n: int) -> int:
      # Initialize DP table
      dp = [initial_value] * (n + 1)

      # Base case
      dp[0] = base_value

      # Fill table iteratively
      for i in range(1, n + 1):
          for choice in choices(i):
              if valid(i, choice):
                  dp[i] = combine(dp[i], dp[prev_state(i, choice)])

      return dp[n]


  # 2D DP example (Longest Common Subsequence)
  def lcs(text1: str, text2: str) -> int:
      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]

recognition_signals:
  - "minimum/maximum"
  - "count ways"
  - "can you reach"
  - "optimal"
  - "longest/shortest"
  - "number of ways"
  - "subset sum"
  - "partition"
  - "knapsack"
  - "sequence"
  - "subsequence"

common_mistakes:
  - title: Incorrect state definition
    description: |
      Choosing a state that doesn't capture all necessary information leads
      to incorrect transitions or missing cases.
    fix: |
      Ask: "What do I need to know to solve this subproblem?" The answer
      defines your state. Test with small examples to verify.

  - title: Wrong base case
    description: |
      Incorrect initialization causes wrong answers to propagate through
      the entire DP table.
    fix: |
      Think about the smallest/simplest subproblem. What's the answer when
      there's nothing left to consider? Start from there.

  - title: Off-by-one in 2D DP
    description: |
      Confusion about whether dp[i] represents the first i elements or the
      element at index i causes index errors.
    fix: |
      Be consistent. Common convention: dp[i] = answer for first i elements,
      so dp[0] = empty case. Indices in strings/arrays are 0-based.

  - title: Forgetting to handle impossible cases
    description: |
      Not returning infinity for minimum problems or 0 for counting when
      a state is unreachable gives wrong aggregations.
    fix: |
      Initialize dp with appropriate "impossible" values (infinity for min,
      -infinity for max, 0 for counting). Return -1 if final answer is
      still impossible.

  - title: Space complexity not optimized
    description: |
      Using O(n*m) space when only the previous row/column is needed
      wastes memory on large inputs.
    fix: |
      If dp[i] only depends on dp[i-1], use two arrays (current and previous)
      or even a single array updated carefully.

variations:
  - name: 1D DP
    description: |
      Single dimension state, typically indexed by position or remaining
      capacity. Common for linear sequences.
    example: "Climbing Stairs, House Robber, Coin Change"

  - name: 2D DP
    description: |
      Two-dimensional state, often for comparing two sequences or tracking
      two variables (position and capacity).
    example: "Longest Common Subsequence, Edit Distance, 0/1 Knapsack"

  - name: Interval DP
    description: |
      State represents a range [i, j]. Solve for all subranges and combine.
      Often O(n^3) time.
    example: "Burst Balloons, Matrix Chain Multiplication"

  - name: Bitmask DP
    description: |
      State includes a bitmask representing a subset. Used when order matters
      among a small set of items.
    example: "Traveling Salesman, Shortest Superstring"

  - name: DP on Trees
    description: |
      State associated with tree nodes. Transition from children to parent
      (or vice versa).
    example: "House Robber III, Binary Tree Maximum Path Sum"

related_patterns:
  - greedy
  - backtracking

prerequisite_patterns:
  - backtracking
