101 Logo
onenoughtone

Tabulation Pattern

What is Tabulation?

Tabulation is a bottom-up dynamic programming technique where we build a table iteratively, starting from the base cases and working our way up to the original problem.

Unlike memoization, which is top-down and uses recursion, tabulation is iterative and builds the solution from the ground up.

When to Use Tabulation

Tabulation is useful when:

  • You need to compute all subproblems
  • You want to avoid recursion stack overflow
  • The problem has a clear iterative structure
  • You need to optimize space complexity

Tabulation Template

  1. Define the state and create a table (array/matrix) to store results
  2. Initialize the base cases in the table
  3. Iterate through the table in a bottom-up manner
  4. Fill each cell using the recurrence relation
  5. Return the final result from the table

Example: Fibonacci

Here's a tabulation solution for computing the nth Fibonacci number:

function fibonacci(n) { // Handle base cases if (n <= 1) return n; // Create a table to store results const dp = new Array(n + 1); // Initialize base cases dp[0] = 0; dp[1] = 1; // Fill the table bottom-up for (let i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } // Return the result return dp[n]; } // Example usage: console.log(fibonacci(10)); // Output: 55 console.log(fibonacci(40)); // Output: 102334155 (would be very slow without DP)

Example: Knapsack Problem

Here's a tabulation solution for the 0/1 knapsack problem:

function knapsack(weights, values, capacity) { const n = weights.length; // Create a 2D table to store results // dp[i][w] represents the maximum value that can be obtained // using the first i items with a knapsack capacity of w const dp = Array(n + 1).fill().map(() => Array(capacity + 1).fill(0)); // Fill the table bottom-up for (let i = 1; i <= n; i++) { for (let w = 0; w <= capacity; w++) { // If the current item is too heavy, skip it if (weights[i - 1] > w) { dp[i][w] = dp[i - 1][w]; } else { // Otherwise, take the maximum of including or excluding the item dp[i][w] = Math.max( dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1] ); } } } // Return the maximum value return dp[n][capacity]; } // Example usage: const weights = [2, 3, 4, 5]; const values = [3, 4, 5, 6]; const capacity = 8; console.log(knapsack(weights, values, capacity)); // Output: 10 (items with values 4 and 6)

Example: Longest Common Subsequence

Here's a tabulation solution for finding the longest common subsequence:

function longestCommonSubsequence(text1, text2) { const m = text1.length; const n = text2.length; // Create a 2D table to store results // dp[i][j] represents the length of LCS of text1[0...i-1] and text2[0...j-1] const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0)); // Fill the table bottom-up for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { // If the current characters match if (text1[i - 1] === text2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { // If they don't match, take the maximum of two possibilities dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } // Return the length of LCS return dp[m][n]; } // Example usage: console.log(longestCommonSubsequence("abcde", "ace")); // Output: 3 (LCS is "ace") console.log(longestCommonSubsequence("abc", "abc")); // Output: 3 (LCS is "abc") console.log(longestCommonSubsequence("abc", "def")); // Output: 0 (no common subsequence)

Pros and Cons of Tabulation

Pros:

  • Avoids recursion and stack overflow issues
  • Often more efficient in terms of constant factors
  • Easier to optimize for space complexity

Cons:

  • May compute unnecessary subproblems
  • Sometimes less intuitive than the recursive approach
  • May be harder to implement for complex state transitions
IntroVisualizePatternsPractice
101 Logo
onenoughtone