Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Dynamic Programming: Recurrence Relations and Memoization

In our previous lesson, we established the conceptual foundation for Dynamic Programming by learning to identify problems with optimal substructure and overlapping subproblems. We saw how a naive recursive approach to problems like calculating Fibonacci numbers leads to an exponential number of redundant calculations.

Today, we will take the next logical step: turning that conceptual understanding into a practical, efficient coding pattern. You will learn how to formulate a recurrence relation for a given problem and then implement a top-down DP solution using memoization. This technique directly tackles the issue of overlapping subproblems and is one of the most intuitive ways to write a DP solution, especially during a coding interview. We'll start with the familiar Fibonacci sequence and then apply the same pattern to another classic problem, Climbing Stairs.

From Recurrence to Recursion to Memoization

The journey to a top-down DP solution follows a clear, three-step path. This pattern is fundamental and, once mastered, can be applied to a wide range of problems.

  1. Formulate the Recurrence Relation: Based on the problem's optimal substructure, define the solution to a larger problem in terms of the solutions to its smaller subproblems.
  2. Write the Brute-Force Recursive Solution: Directly translate the recurrence relation into a recursive function. This solution will be correct but inefficient.
  3. Add Memoization: Introduce a cache (an array or a hash map) to store the results of subproblems. This avoids re-computation and transforms the exponential solution into a polynomial one.

Let's walk through this process with the Fibonacci sequence.

The Fibonacci Example Revisited

In the last lesson, we identified the recurrence for Fibonacci numbers as , with base cases and . A direct recursive translation leads to the massive tree of overlapping computations we saw previously.

A recap of the recursion tree for F(5). The highlighted green areas show identical subproblems being computed multiple times, leading to exponential time complexity.

Now, let's see how to fix this inefficiency. The core idea of memoization is simple: if you've already solved a subproblem, just look up the answer instead of solving it again.

The following video provides an excellent, structured walkthrough of how to convert a plain recursive solution into a memoized one. It introduces a clear, step-by-step method that you can use as a template for many DP problems.

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

This video from the "take U forward" channel clearly explains the concept of overlapping subproblems and then demonstrates a three-step process to implement a memoized DP solution.

First, watch the segment from the recursion tree explanation. This part visually reinforces the concept of overlapping subproblems that we discussed in the last lesson. Next, focus on the core logic of memoization from where memoization is introduced. The presenter outlines three key steps: Step 0: Declare a data structure (a DP array) to store results. Step 1: Before returning from a recursive call, store the computed answer in the DP array. Step 2: At the beginning of a recursive call, check if the answer for the current subproblem is already in the DP array. If it is, return it directly without re-computing. This structured approach is a powerful pattern for your problem-solving toolkit.

The article below provides a detailed implementation of this exact logic in Python. It walks through the naive approach, analyzes its inefficiency, and then presents the optimized code using memoization.

Fibonacci Number (LeetCode 509, DP & Memoization) - Codetree

This article from Codetree provides a complete walkthrough for solving the Fibonacci problem with top-down DP.

Start by reviewing the problem statement and naive idea. This formalizes the recurrence relation we've been using. Then, read the section on memoization. This explains the logic of using a cache (called memo) to store results. Study the Python implementation carefully. Notice how it directly maps to the steps described in the video: a check at the beginning of the function and a store-before-return at the end. Finally, read the complexity analysis. Understanding why the time complexity drops from O(2^N) to O(N) is crucial.

With memoization, the recursion tree is effectively "pruned." Once a subproblem like fib(2) is computed, any subsequent call to it immediately returns the stored value, cutting off that entire branch of the recursion tree.

The recursion tree for `fib(4)` with memoization. The red arrow shows that when `fib(2)` is needed a second time, its value is retrieved from the cache (`@memo[2]`) instead of being recomputed, avoiding the redundant calls to `fib(1)` and `fib(0)`.

Applying the Pattern: The Climbing Stairs Problem

Now let's apply this exact pattern to a new problem that, on the surface, might not look like Fibonacci. This is where your pattern recognition skills, honed from solving LeetCode problems, come into play.

Problem: You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Step 1: Formulate the Recurrence

Let's define ways(n) as the number of distinct ways to climb a staircase of n steps.
To reach the n-th step, what was your final move?

  • You could have taken a single step from step n-1.
  • Or, you could have taken two steps from step n-2.

The total number of ways to reach step n is the sum of the ways to reach step n-1 and the ways to reach step n-2. This gives us a recurrence relation:

This is exactly the same recurrence structure as the Fibonacci sequence! The only difference is the base cases.

  • ways(1): 1 way (a single 1-step climb).
  • ways(2): 2 ways (1-step + 1-step, or a single 2-step climb).

Steps 2 & 3: Implement with Memoization

Now that we have the recurrence, we can directly implement a memoized solution. The following resource provides a clear walkthrough, starting from the brute-force recursion and evolving it to the optimized top-down DP solution.

Climbing Stairs | Leetcode 70 | Solved using Dynamic ...

This article breaks down the Climbing Stairs problem perfectly, following our three-step process.

First, read Solution 1: Brute Force Approach. Pay close attention to the Intuition and Code Explanation to see how the recurrence relation is translated into a simple recursive function. Examine the Dry Run for n=4 in that section. The key line is at the end: "Notice: func(2)... are recalculated multiple times!". This is the explicit demonstration of overlapping subproblems. Now, move to Solution 2: Memoization Approach. Read the Intuition and Code Explanation. This shows the addition of the dp array (our memoization cache). Finally, trace the Dry Run for the memoized solution. The crucial moment is when func(2, dp) is called a second time. Instead of re-computing, the code returns the cached value. This is the "aha!" moment of memoization.

To get another perspective, the following video provides a fantastic visualization of the recursion tree for the Climbing Stairs problem and how memoization dramatically prunes it.

The Recursive Staircase - Top Down & Bottom Up Dynamic Programming ("Climbing Stairs" on LeetCode)

This video from Back To Back SWE gives a very intuitive, visual explanation of the top-down approach for Climbing Stairs.

First, watch the explanation of the top-down approach without memoization. The presenter builds the entire recursion tree, showing all the redundant computations. Then, watch the key segment on how memoization optimizes the tree. The visualization of "cutting out" entire subtrees because their solutions are already known is a powerful way to understand the efficiency gains.

Conclusion

In this lesson, you translated the theoretical properties of DP into a concrete implementation strategy. The top-down approach with memoization is a powerful and intuitive pattern: it preserves the natural, problem-solving structure of recursion while eliminating its primary inefficiency.

Key Takeaways:

  • Top-Down DP Pattern:
    1. Define a recurrence relation based on optimal substructure.
    2. Implement it as a plain recursive function.
    3. Augment the function with a cache (memo) to store and retrieve subproblem results, avoiding re-computation.
  • The Memoization Logic: The pattern involves two key additions to a recursive function:
    1. Check the cache: At the start of the function, check if the solution for the current state is already computed. If so, return it.
    2. Save to cache: Before returning a newly computed result, store it in the cache.
  • Pattern Recognition: Problems like Climbing Stairs can be revealed to be structurally identical to Fibonacci once you derive their recurrence relation. Recognizing these underlying patterns is a key skill.

In our next lesson, we'll explore the second major DP implementation technique: bottom-up dynamic programming, also known as tabulation. It solves the same problems but uses an iterative approach, building solutions from the smallest subproblems upwards.

Can't find a good explanation? Sign up and we'll make it for you

Sign up