Pattern #68

2D Dynamic Programming

Solve complex subproblems with two changing parameters. Master Grid DP, Two-Sequence Matching, 2D Knapsack, Interval DP, and row space compression.

Must Solve

20 core questions — solve these first.

  1. 1.Unique Paths
    medium
  2. 2.Unique Paths II
    medium
  3. 3.Minimum Path Sum
    medium
  4. 4.Longest Common Subsequence
    medium
  5. 5.Edit Distance
    medium
  6. 6.Longest Palindromic Subsequence
    medium
  7. 7.Distinct Subsequences
    hard
  8. 8.Interleaving String
    medium
  9. 9.0/1 Knapsack
    medium
  10. 10.Partition Equal Subset Sum — 2D version
    medium
  11. 11.Target Sum — 2D thinking
    medium
  12. 12.Triangle
    medium
  13. 13.Minimum Falling Path Sum
    medium
  14. 14.Maximal Square
    medium
  15. 15.Dungeon Game
    hard
  16. 16.Longest Common Substring
    medium
  17. 17.Regular Expression Matching
    hard
  18. 18.Wildcard Matching
    hard
  19. 19.Palindrome Partitioning DP
    medium
  20. 20.Cherry Pickup
    hard

Also Important

10 advanced 2D DP variations worth practicing.

  1. 21.Matrix Chain Multiplication
    hard
  2. 22.Burst Balloons
    hard
  3. 23.Stone Game variants
    medium
  4. 24.Boolean Parenthesization
    hard
  5. 25.Egg Dropping
    hard
  6. 26.Longest Repeating Subsequence
    medium
  7. 27.Shortest Common Supersequence
    hard
  8. 28.Count Palindromic Subsequences
    hard
  9. 29.DP on Two Sequences
    medium
  10. 30.Interval DP
    hard

How to Think

Before building matrix loops, finish: dp[i][j] means __________ explicitly.

  1. Two things change in recursive state?dp[i][j]
  2. Grid row + column?2D Grid DP
  3. Two strings / sequences?2D String DP dp[i][j]
  4. Item index + capacity?2D Knapsack DP
  5. Substring / subsegment range (left, right)?Interval 2D DP

Go Quick Reference — 2D DP

Compilable Go code demonstrating Edit Distance 2D matrix DP and Space-Optimized Unique Paths O(N) row compression.

package main

import "fmt"

// Edit Distance 2D Matrix DP: O(M * N) time and space
func minDistance(word1 string, word2 string) int {
    m, n := len(word1), len(word2)
    dp := make([][]int, m+1)
    for i := range dp {
        dp[i] = make([]int, n+1)
    }

    // Base Case Rows and Columns (Empty String conversions)
    for i := 0; i <= m; i++ { dp[i][0] = i }
    for j := 0; j <= n; j++ { dp[0][j] = j }

    for i := 1; i <= m; i++ {
        for j := 1; j <= n; j++ {
            if word1[i-1] == word2[j-1] {
                dp[i][j] = dp[i-1][j-1]
            } else {
                // min(Insert, Delete, Replace)
                ins := dp[i][j-1]
                del := dp[i-1][j]
                rep := dp[i-1][j-1]
                minOp := ins
                if del < minOp { minOp = del }
                if rep < minOp { minOp = rep }
                dp[i][j] = 1 + minOp
            }
        }
    }
    return dp[m][n]
}

// Space-Optimized 2D Grid DP (Unique Paths): O(Cols) space
func uniquePathsSpaceOptimized(m int, n int) int {
    dp := make([]int, n)
    for j := 0; j < n; j++ { dp[j] = 1 } // Row 0 base case

    for i := 1; i < m; i++ {
        for j := 1; j < n; j++ {
            dp[j] = dp[j] + dp[j-1] // top (dp[j]) + left (dp[j-1])
        }
    }
    return dp[n-1]
}

func main() {
    fmt.Println("Edit Distance:", minDistance("horse", "ros"))      // 3
    fmt.Println("Unique Paths:", uniquePathsSpaceOptimized(3, 7)) // 28
}

👉 2D DP: O(M × N) time and O(M × N) or O(N) compressed space.

5 Major 2D DP Categories & Mechanics

1. Grid & Two-Sequence DP

• Grid DP: dp[r][c] represents cell reachable by top or left.
• Two-Sequence DP: dp[i][j] compares prefix i of A and prefix j of B (LCS, Edit Distance, Interleaving).

2. Knapsack & Interval DP

• Knapsack DP: dp[i][w] tracks best value using first i items under capacity w.
• Interval DP: dp[l][r] stores answer for substring/subsegment range from l to r.

Visual Memory Rule
Define dp[i][j] Cell Meaning → Base Row/Col → Transition → Target Cell
Grid DP → dp[r][c] depends on ↑ top, ← left
Two-String DP → dp[i][j] depends on ↖ diagonal, ↑ top, ← left
Interval DP → Filled by subsegment length (1 to N) along upper diagonals
Space Optimization → Compress 2D matrix O(M*N) to 1D row array O(N)

2D DP Fill Orders & Compression

Fill Order Follows Dependencies

• Top/Left Dependencies: Fill top-left to bottom-right (row by row).
• Interval DP (Palindrome, Burst Balloons): Fill by substring length (1, 2, 3...) along upper diagonals!
• Always compute dependencies before the target cell reads them.

2D to 1D Row Compression

When row i reads only row i-1:
Keep a single row array dp[cols].
Before update dp[j] represents top; after update dp[j-1] represents left.
Reduces memory from O(Rows * Cols) to O(Cols).

Base Rows and Columns Initialization

In string matching DP (Edit Distance, LCS), allocating (N+1) × (M+1) creates explicit base boundaries:
• Row 0 = Empty prefix of String A.
• Column 0 = Empty prefix of String B.
This eliminates messy out-of-bounds guards inside the main double loop.

Common Interview Mistakes

  • • Coding 2D nested loops without explicitly defining what dp[i][j] represents.
  • • Wrong base row or column initializations (e.g. failing to set Edit Distance base delete/insert costs).
  • • Failing to use (N+1) x (M+1) dimensions for string matching DP, causing index bounds errors.
  • • Confusing string character index s[i-1] with 1-based DP prefix index dp[i][j].
  • • Filling Interval DP row-by-row instead of by increasing subsegment length.
  • • Assuming the final answer is always bottom-right cell dp[N][M] (some problems require max across table).
  • • Compressing 2D space to 1D prematurely before validating diagonal dependency requirements.
  • • Confusing directional DP grids (top/left movement) with general 4-directional BFS graph shortest paths.
  • • Allocating a massive 2D matrix without checking memory constraints (Memory Limit Exceeded).

Interview Rules

Two changing state variables → 2D DP
Rule 1                       → Define dp[i][j] cell meaning explicitly
Rule 2                       → Allocate (N+1) x (M+1) for string matching prefix bases
Rule 3                       → Determine dependency direction (↑ top, ← left, ↖ diagonal)
Rule 4                       → Interval DP = Fill diagonally by subsegment length 1 to N
Rule 5                       → Verify if target is dp[M][N] or max(dp matrix)
Rule 6                       → Compress 2D to 1D row array after table logic works
Time Complexity              → Cells × Work per Cell (O(N*M) or O(N³))
Space Complexity             → O(N*M) Matrix or O(M) Compressed Row

💡 Golden Rule: When two changing values are needed to completely describe the subproblem, make them the two DP dimensions and fill each cell only after the states it depends on are ready.

Production Thinking

Sequence Alignment & Diff Engines → git diffs, document comparison, and DNA sequence matching rely on LCS and Edit Distance 2D matrices.

Multi-Resource Allocation → 2D Knapsack tables optimize item choices under combined budget and storage constraints.

Grid Path Precomputation → directional 2D DP matrices precompute optimal traversal costs for fast lookup queries.

Production warning → 2D matrix memory scales with N × M; huge matrices (e.g. 10,000 × 10,000) will trigger Out-Of-Memory crashes without space compression.

Remember This

2D DP State: dp[i][j] (Grid, Two Strings, Knapsack, Intervals)
Golden Steps: Define Cell → Base Row/Col → Dependencies → Fill Order → Target
String DP: Use (N+1) x (M+1) table for empty string base cases
Dependencies: ↑ top, ← left, ↖ diagonal
Interval DP: Fill diagonally by subsegment length 1..N
Row Compression reduces O(N*M) memory to O(M)