Pattern #66

Tabulation

Build solutions from base cases up to the final target. Compute states sequentially in dependency order without recursion stack overhead.

Must Solve

20 core questions — solve these first.

  1. 1.Fibonacci Number
    easy
  2. 2.Climbing Stairs
    easy
  3. 3.Min Cost Climbing Stairs
    easy
  4. 4.House Robber
    medium
  5. 5.Coin Change
    medium
  6. 6.Coin Change II
    medium
  7. 7.Partition Equal Subset Sum
    medium
  8. 8.0/1 Knapsack
    medium
  9. 9.Unbounded Knapsack
    medium
  10. 10.Unique Paths
    medium
  11. 11.Minimum Path Sum
    medium
  12. 12.Longest Common Subsequence
    medium
  13. 13.Edit Distance
    medium
  14. 14.Longest Increasing Subsequence
    medium
  15. 15.Decode Ways
    medium
  16. 16.Word Break
    medium
  17. 17.Longest Palindromic Subsequence
    medium
  18. 18.Target Sum
    medium
  19. 19.Triangle Minimum Path Sum
    medium
  20. 20.Maximal Square
    medium

Also Important

10 advanced tabulation variations worth practicing.

  1. 21.Perfect Squares
    medium
  2. 22.Combination Sum IV
    medium
  3. 23.Distinct Subsequences
    hard
  4. 24.Interleaving String
    medium
  5. 25.Minimum Falling Path Sum
    medium
  6. 26.Dungeon Game
    hard
  7. 27.Stock Buy/Sell DP
    medium
  8. 28.Palindrome DP
    medium
  9. 29.Interval DP
    hard
  10. 30.DP Space Optimization
    medium

How to Think

Start from known base answers and iterate through subproblems in topological dependency order.

  1. Can I start from known base answers?Tabulation
  2. Can I compute states in dependency order?Bottom-Up DP
  3. Want to avoid function call stack overhead?Tabulation
  4. Dependencies move left-to-right?Loop 0 to N
  5. 0/1 Knapsack space optimization?Loop capacity Right-to-Left

Go Quick Reference — Tabulation

Compilable Go implementation demonstrating 2D LCS matrix tabulation and 1D rolling array space optimization.

package main

import "fmt"

// 2D Matrix Tabulation for Longest Common Subsequence
func lcsTabulation(text1, text2 string) int {
    m, n := len(text1), len(text2)
    // Create DP table initialized to 0 (base cases for empty strings)
    dp := make([][]int, m+1)
    for i := range dp {
        dp[i] = make([]int, n+1)
    }

    // Fill in dependency order (top-left to bottom-right)
    for i := 1; i <= m; i++ {
        for j := 1; j <= n; j++ {
            if text1[i-1] == text2[j-1] {
                // Invariant: match comes from ↖ diagonal
                dp[i][j] = 1 + dp[i-1][j-1]
            } else {
                // Invariant: mismatch comes from max of ↑ top or ← left
                if dp[i-1][j] > dp[i][j-1] {
                    dp[i][j] = dp[i-1][j]
                } else {
                    dp[i][j] = dp[i][j-1]
                }
            }
        }
    }
    return dp[m][n]
}

// 1D Rolling Array Space Optimization O(N) space
func lcsSpaceOptimized(text1, text2 string) int {
    m, n := len(text1), len(text2)
    prev := make([]int, n+1)
    curr := make([]int, n+1)

    for i := 1; i <= m; i++ {
        for j := 1; j <= n; j++ {
            if text1[i-1] == text2[j-1] {
                curr[j] = 1 + prev[j-1]
            } else {
                if prev[j] > curr[j-1] {
                    curr[j] = prev[j]
                } else {
                    curr[j] = curr[j-1]
                }
            }
        }
        copy(prev, curr)
    }
    return prev[n]
}

func main() {
    fmt.Println("LCS 2D Table:", lcsTabulation("abcde", "ace"))        // 3
    fmt.Println("LCS 1D Optimized:", lcsSpaceOptimized("abcde", "ace")) // 3
}

👉 Bottom-Up Matrix: O(M × N) time and O(M × N) or O(N) space.

Core Tabulation Structure & Order

The 5 Tabulation Steps

1. Allocate Table: Initialize array or matrix dimensions.
2. Set Base Cases: Fill known starting cells (e.g. dp[0] = 1 or INF).
3. Determine Order: Ensure dependent cells are computed first.
4. Fill Loops: Execute state transition formulas.
5. Return Target: Return final cell dp[target].

Dependency Direction Rule

• If dp[i] depends on dp[i-1] → Loop Left to Right (i = 0 to N).
• If dp[i] depends on dp[i+1] → Loop Right to Left (i = N down to 0).
• Always compute dependencies before the cell that reads them!

Visual Memory Rule
Base Cases → Choose Order → Fill Loops → Target Cell
0/1 Knapsack 1D Table → Loop capacity Right-to-Left
Unbounded Knapsack 1D Table → Loop capacity Left-to-Right
2D Matrix Matching → dp[i][j] uses ↖ diagonal, ↑ top, ← left
Space Optimization → Rolling arrays reduce dimensions after table verification

Tabulation Variants & Loop Dynamics

0/1 vs Unbounded 1D Loop Direction

• 0/1 Knapsack (Single Item Use): Capacity loop runs Right-to-Left (w = W down to weight) so dp[w-weight] reads previous item values.
• Unbounded Knapsack (Multiple Reuses): Capacity loop runs Left-to-Right (w = weight up to W) so newly updated values can be reused.

Interval & Matrix Fill Order

• Interval DP (Burst Balloons, Palindromes): Fills table by substring length (1 to N) rather than standard row-by-row.
• Grid DP: Standard top-left to bottom-right matrix loop.

Loop Order Changes Mathematical Meaning

In counting DP problems (like Coin Change II):
• Outer loop = coins, Inner loop = amount: Counts Combinations (order does not matter).
• Outer loop = amount, Inner loop = coins: Counts Permutations (order matters).
Warning: Changing loop nesting order alters the mathematical definition of your DP cells!

Common Interview Mistakes

  • • Filling the table before explicitly defining what dp[cell] represents.
  • • Wrong base case initializations (e.g. initializing min problems with 0 instead of INF, or combinations with 0 instead of dp[0]=1).
  • • Incorrect iteration order where a cell is computed before its dependencies are ready.
  • • Running 0/1 Knapsack 1D capacity loop left-to-right (accidentally allowing unlimited item reuse).
  • • Failing to realize that changing outer/inner loop nesting converts combinations into permutations.
  • • Overwriting DP values prematurely during space optimization before previous row values are fully read.
  • • Allocating a massive 2D matrix without checking constraint sizes (causing Memory Limit Exceeded).
  • • Forcing bottom-up tabulation on tree problems when postorder DFS memoization is cleaner.
  • • Off-by-one errors on string DP tables (forgetting +1 size for empty prefix base cases).
  • • Returning dp[N] when the target is at dp[N-1] or vice-versa.

Interview Rules

Start from Base Cases → Fill Bottom-Up Table
Rule 1                → Define dp[state] cell meaning explicitly
Rule 2                → Initialize base case boundary cells
Rule 3                → Determine dependency direction before looping
Rule 4                → 0/1 Knapsack 1D = Right-to-Left loop
Rule 5                → Unbounded Knapsack 1D = Left-to-Right loop
Rule 6                → Space optimization: reduce table after table works
Time Complexity       → Total States × Transitions per State
Space Complexity      → Table Size (O(N*M), O(N), or O(1))

💡 Golden Rule: Start from states whose answers you already know, then fill every new state only after all states it depends on are ready.

Production Thinking

Batch Optimization & Precomputation → precomputing cost tables bottom-up allows instant O(1) query lookups in production services.

Sequence Alignment & Text Diff Engines → git diffs and DNA sequence alignment use 2D bottom-up tabulation tables for sequential memory locality.

Resource Allocation Systems → tabulation systematically computes optimal resource usage across all discrete capacity thresholds.

Production warning → tabulation allocates full matrices; check state count (N × M) before allocating to avoid out-of-memory crashes.

Remember This

Tabulation = Bottom-Up DP (Base Cases → Target)
5-Step Pattern: Table → Base → Order → Fill → Target
0/1 Knapsack 1D = Right-to-Left loop
Unbounded Knapsack 1D = Left-to-Right loop
Loop order changes combinations vs permutations
Sequential memory locality makes tabulation fast
Calculate state count before allocating large tables