Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 17

Fibonacci: Tabulation vs. Memoization

Recommended resources

Welcome back. In our previous lesson, we tackled the exponential complexity of the naive recursive Fibonacci solution by introducing memoization. This "top-down" dynamic programming approach preserved the recursive structure while using a cache to store subproblem results, bringing the time complexity down to a swift linear .

Today, we will explore the second fundamental approach to dynamic programming: tabulation. This "bottom-up" method eliminates recursion entirely, building the solution iteratively from the ground up. Our goals are to implement a tabulation-based solution for the Fibonacci sequence, analyze its performance, and draw a clear comparison between it and memoization. This will equip you with two powerful and distinct perspectives for solving DP problems.

From Top-Down to Bottom-Up: The Tabulation Philosophy

In memoization, we started with the problem fib(n) and let recursion break it down, catching and storing results along the way. Tabulation flips this script. It embodies a "bottom-up" philosophy:

  1. Identify the smallest, simplest subproblems (the base cases).
  2. Solve them first.
  3. Use their solutions to iteratively solve slightly larger subproblems.
  4. Continue this process until you have built your way up to the solution for the original problem, fib(n).

Instead of a recursion tree that branches downwards, imagine building a table of results, filling it row by row from the beginning. This iterative nature makes tabulation straightforward to implement with a simple loop, which has the added benefit of avoiding the overhead and potential stack depth limitations of recursion.

The following video from Abdul Bari provides an excellent illustration of this bottom-up construction.

4 Principle of Optimality - Dynamic Programming introduction

This video masterfully demonstrates the iterative, bottom-up nature of tabulation.

Watch the segment from the iterative function for Fibonacci. Observe how he uses an array and a loop to fill the table starting from the base cases F(0) and F(1) and building up to F(5).


Implementing Tabulation

To translate the tabulation approach into C++ code, we follow a clear, three-step process. We will use a std::vector to serve as our "table."

The "takeuforward" article provides a concise guide to these steps.

Dynamic Programming Introduction - Tutorial

This article breaks down the tabulation method into simple, actionable steps.

Please read Part -2: Tabulation. Focus on the three steps listed, as they provide a general template for any bottom-up DP solution.

Let's formalize these steps for our Fibonacci problem:

  1. Declare a DP Array: Create a vector, dp, of size n + 1. This vector will store the Fibonacci numbers from 0 to n. For example, dp[i] will hold the value of the i-th Fibonacci number.

  2. Initialize Base Cases: Set the initial known values directly in the array. For Fibonacci, these are dp[0] = 0 and dp[1] = 1.

  3. Iterate and Fill: Loop from i = 2 up to n. In each iteration, calculate dp[i] using the values we have already computed, which are stored in our dp array. The recurrence relation tells us dp[i] = dp[i-1] + dp[i-2].

After the loop completes, the answer to our problem, fib(n), is simply the value stored at dp[n].

The following C++ code from the cp-algorithms resource demonstrates this elegant implementation.

Introduction to Dynamic Programming

This resource shows a clean, iterative C++ implementation.

In the section Bottom-up Dynamic Programming, examine the C++ code block that follows. Notice how it directly implements the three steps: array creation, base case initialization, and the iterative loop.


Memoization vs. Tabulation: A Detailed Comparison

You now know two ways to solve the same DP problem. While they often achieve the same time complexity, their underlying mechanisms and trade-offs are different. Understanding these distinctions is crucial for choosing the right approach in a competitive programming context.

Aspect Memoization (Top-Down) Tabulation (Bottom-Up)
Approach Starts with the final state f(n) and recursively breaks it down. Starts with the base cases f(0), f(1) and iteratively builds up to f(n).
Implementation Recursive. Modifies a plain recursive function by adding a cache. Iterative. Typically uses for loops. No recursion.
State Evaluation Computes only the states required to solve the problem. Computes all states from the base case up to n.
Stack Space Uses recursion stack space, which can be an issue for deep recursions (can cause Stack Overflow). Space: . No recursion stack is used. Space is constant in this regard.
Overhead Function call overhead for each recursive step. Generally faster due to no recursive call overhead.

For the Fibonacci problem, both methods have a time complexity of . However, tabulation's space complexity is (for the DP array), while memoization's is (for the DP array) + (for the recursion stack), which still simplifies to but has a larger constant factor and the risk of stack overflow. For these reasons, tabulation is often preferred in competitive programming.

The take U forward video offers another great breakdown of this comparison.

DP 1. Introduction to Dynamic Programming | Memoization | Tabulation | Space Optimization Techniques

This video provides a step-by-step conversion from the recursive logic to a tabulation implementation.

Watch the segment from converting to tabulation. The presenter clearly outlines the logic for building the bottom-up solution and compares its time and space complexity to the memoized version, highlighting the elimination of the recursion stack space.


A Final Touch: Space Optimization

Let's re-examine the tabulation recurrence: dp[i] = dp[i-1] + dp[i-2].

Notice that to compute the value for the current index i, we only need the values at i-1 and i-2. We never need dp[i-3] or any earlier values again. This suggests that storing the entire dp array is wasteful. We can optimize the space complexity from to !

The idea is to use just two variables to track the previous two values. Let's call them prev2 (for fib(i-2)) and prev (for fib(i-1)).

The process inside the loop becomes:

  1. Calculate current = prev + prev2.
  2. Update the variables for the next iteration: prev2 becomes what prev was, and prev becomes the current value we just calculated.
  3. Repeat until we reach n.
This diagram shows how we only need to track the two previous values to calculate the current one, allowing us to replace the full DP array with a few variables.

This simple optimization is a powerful technique you'll see often. The take U forward resources provide a clear explanation and implementation of this idea.

Dynamic Programming Introduction - Tutorial

This section explains and codes the space-optimization technique.

Read Part 3: Space Optimization. Pay close attention to the illustration and how the variables prev, prev2, and curr are updated within the loop. The C++ code block below shows this in action.

After this optimization, our Fibonacci solution has a time complexity of and a space complexity of —a significant improvement and the most efficient solution for this problem.


Conclusion

In this lesson, you've learned the bottom-up approach to dynamic programming, tabulation. By starting with base cases and iteratively building up the solution, we eliminated recursion and its associated overhead. We then took it a step further, optimizing the space from linear to constant.

Key Takeaways:

  • Tabulation is a bottom-up, iterative DP method that builds solutions from base cases.
  • It is often implemented with a for loop and a DP array/vector, avoiding the risk of stack overflow.
  • For Fibonacci, both memoization and tabulation achieve time, but tabulation is slightly more space-efficient by not using the recursion stack.
  • The space for tabulation can often be optimized. For Fibonacci, by observing that we only need the last two results, we reduced space complexity from to .

You are now familiar with the two main pillars of dynamic programming. In our next lesson, we will begin applying these foundational patterns to solve classic competitive programming problems, starting with a 1D DP problem called "Climbing Stairs".