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.Unique Paths2D Grid DP: dp[r][c] = dp[r-1][c] + dp[r][c-1]medium
- 2.Unique Paths IIGrid DP with obstacles: if obstacle dp[r][c] = 0, else top + leftmedium
- 3.Minimum Path SumGrid DP: dp[r][c] = grid[r][c] + min(dp[r-1][c], dp[r][c-1])medium
- 4.Longest Common Subsequence2D String DP: match -> 1 + dp[i-1][j-1], else max(top, left)medium
- 5.Edit Distance2D String DP: match -> dp[i-1][j-1], else 1 + min(insert, delete, replace)medium
- 6.Longest Palindromic Subsequence2D Substring DP: match -> 2 + dp[i+1][j-1], else max(top, left)medium
- 7.Distinct Subsequences2D String DP counting: match -> dp[i-1][j-1] + dp[i-1][j]hard
- 8.Interleaving String2D String Boolean DP: dp[i][j] checks prefix match of s1 and s2 with s3medium
- 9.0/1 Knapsack2D State dp[i][w]: max(skip dp[i-1][w], take val + dp[i-1][w-weight])medium
- 10.Partition Equal Subset Sum — 2D version2D Boolean DP: dp[i][s] checks if subset sum s is possible using first i itemsmedium
- 11.Target Sum — 2D thinking2D State dp[i][sum]: count ways using first i numbersmedium
- 12.TriangleGrid DP accumulation: dp[r][c] += min(dp[r+1][c], dp[r+1][c+1])medium
- 13.Minimum Falling Path SumGrid DP: dp[r][c] = cell + min(left, center, right)medium
- 14.Maximal Square2D Matrix: dp[r][c] = 1 + min(top, left, diagonal) for 1smedium
- 15.Dungeon GameReverse Grid DP: compute min health required from bottom-right to top-lefthard
- 16.Longest Common Substring2D Matrix: match -> 1 + dp[i-1][j-1], else reset to 0medium
- 17.Regular Expression Matching2D String Matching matrix handling . and * wildcardshard
- 18.Wildcard Matching2D String Matching matrix handling ? and * wildcardshard
- 19.Palindrome Partitioning DP2D Substring Palindrome table + 1D min cuts DPmedium
- 20.Cherry Pickup3D/2D Grid DP with 2 simultaneous paths: dp[r1][c1][r2]hard
Also Important
10 advanced 2D DP variations worth practicing.
- 21.Matrix Chain MultiplicationInterval 2D DP: min cost to multiply matrix chain from i to jhard
- 22.Burst BalloonsInterval 2D DP: last balloon burst k in range (i, j)hard
- 23.Stone Game variantsInterval Game DP: max score difference dp[i][j]medium
- 24.Boolean ParenthesizationInterval DP: count true/false evaluations of boolean expressionhard
- 25.Egg Dropping2D State dp[eggs][floors]: min moves in worst case scenariohard
- 26.Longest Repeating SubsequenceLCS 2D variant on same string with i != j constraintmedium
- 27.Shortest Common Supersequence2D LCS table path reconstruction to build supersequencehard
- 28.Count Palindromic SubsequencesInterval 2D DP counting total palindromic subsequenceshard
- 29.DP on Two Sequences2D Matrix alignment across two dynamic sequencesmedium
- 30.Interval DPGeneric diagonal fill DP by substring/subsegment lengthhard
How to Think
Before building matrix loops, finish: dp[i][j] means __________ explicitly.
- Two things change in recursive state?dp[i][j]
- Grid row + column?2D Grid DP
- Two strings / sequences?2D String DP dp[i][j]
- Item index + capacity?2D Knapsack DP
- 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.
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)