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 Essentials

Welcome to our new module on Dynamic Programming. In our previous work on graphs, we focused on traversing and analyzing relationships between distinct entities. Now, we shift our perspective to a powerful algorithmic paradigm designed for optimization and counting problems. Dynamic Programming (DP) is often seen as one of the more challenging topics in algorithms, but its reputation comes from the initial conceptual hurdle. Once you grasp its core principles, a whole new category of problems becomes solvable.

This first lesson is all about building that foundational understanding. We won't be writing complex code just yet. Instead, our goal is to learn how to recognize a problem that is suitable for a dynamic programming solution. Specifically, you will learn to identify the two critical properties that define a DP problem: optimal substructure and overlapping subproblems. Mastering this identification step is the key to unlocking the entire DP pattern.

What is Dynamic Programming?

At its heart, Dynamic Programming is an optimization technique. It's a method for solving complex problems by breaking them down into a collection of simpler, smaller subproblems. The key is that these subproblems are not solved in isolation; their results are stored and reused to avoid redundant calculations, which is what gives DP its remarkable efficiency.

Dynamic Programming Interview Questions & Tips

This article from interviewing.io provides a concise introduction to DP and its core conditions.

First, read the introductory section, What is Dynamic Programming?, to get a high-level overview. Then, jump to the section that formally defines the two conditions we will be exploring throughout this lesson. We will dissect these concepts in detail next.

As you just read, for a problem to be solvable with DP, it must exhibit two specific characteristics. Let's explore each one using the classic example of the Fibonacci sequence.

1. Overlapping Subproblems

A problem is said to have overlapping subproblems if a simple recursive algorithm solves the same subproblems over and over again. This repetition leads to massive inefficiency.

Consider the standard recursive function to find the n-th Fibonacci number, where , with base cases and .

A naive implementation would look like this:

def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

If we call fib(5), the function will compute fib(4) and fib(3). But to compute fib(4), it will again compute fib(3) and fib(2). The calculation for fib(3) is performed twice from scratch. This redundancy gets exponentially worse as n increases.

The recursion tree for fib(5) makes this wastefulness visually apparent.

The recursion tree for `fib(5)`. Notice how entire branches, such as the one for `fib(3)`, are computed multiple times. These are the "overlapping subproblems".

The video below provides an excellent animated walkthrough of this exact issue and introduces the idea of "memoization" (which we will cover in the next lesson) as the solution.

Mastering Dynamic Programming - How to solve any interview problem (Part 1)

This video from Tech With Nikola clearly illustrates the inefficiency of the naive recursive approach for Fibonacci and how storing results can fix it.

First, watch the explanation of the naive recursive solution and its visualization from the beginning to the end of the complexity discussion. Pay close attention to the call tree animation, as it highlights the repeated computations. Then, watch the following segment on memoization. This introduces the core idea of DP: compute each subproblem only once and store its result for future use.

2. Optimal Substructure

A problem has optimal substructure if its globally optimal solution can be constructed from the optimal solutions of its subproblems.

Let's look at the Fibonacci sequence again. The solution for is built directly from the solutions for and . We don't need to know how or were calculated; we only need their final, optimal values. This property of being able to build a solution from the solutions of smaller sub-problems is the essence of optimal substructure. It is what allows us to formulate a recurrence relation, which is the mathematical heart of a DP solution.

This might sound abstract, so let's use a more visual analogy that ties into our previous module on graphs. Many DP problems can be modeled as finding the "best" path in a Directed Acyclic Graph (DAG).

Consider this as a graph where we want to find the longest path from A to J. The value F(X) represents the length of the longest path starting from node X. To find the longest path from A, we look at its neighbors (B, C, D). The optimal path from A will be via the neighbor that leads to the overall longest path. For instance, the path through B would have a total length of `2 (edge A->B) + F(B) = 2 + 11 = 13`. The path through C is `4 + F(C) = 4 + 7 = 11`. The path through D is `3 + F(D) = 3 + 8 = 11`. We would choose the path through B. The crucial insight is that the decision at node A relies on the pre-computed **optimal** solutions for its subproblems (the paths starting from B, C, and D).

This property is a non-negotiable prerequisite for dynamic programming.

Dynamic Programming Pattern

This resource from Berkeley provides a more formal computer science perspective on the structure of DP problems.

First, read the step Find a recursive relation. It emphasizes that the first step is to prove that an optimal solution can be built from optimal sub-solutions. Next, read the short section on the Precondition. This reinforces that optimal substructure is the fundamental requirement.

A Framework for Identifying DP Problems

Given your experience with LeetCode, you know that pattern recognition is key. So, how do you spot these two properties in the wild? The following video presents a 5-step framework. For today, we will focus on the first three steps, which are all about identification.

5 Simple Steps for Solving Dynamic Programming Problems

This video from Reducible provides a structured way to approach DP problems, using the Longest Increasing Subsequence (LIS) problem as an example.

Watch the introduction from the start of the video. Follow the detailed walkthrough of the LIS problem from Step 1: Visualize and Step 2: Subproblems. Pay close attention to how the problem is visualized as a DAG and how the subproblem is defined as "the LIS ending at index k". This is a classic subproblem definition pattern. Continue with Step 3: Find Relationships. This is where optimal substructure becomes concrete. The video shows that the solution to a subproblem (LIS ending at index 4) is built upon the solutions to smaller, preceding subproblems.

Heuristics for Identification:

From the videos and readings, we can extract some practical tips for your interviews and problem-solving sessions:

  • Look for "min/max/count" problems: Does the question ask for the minimum cost, maximum profit, longest sequence, or total number of ways to do something? These are strong indicators for DP.
  • A slow recursive solution hints at DP: If you can quickly devise a brute-force recursive solution that is obviously exponential (because it branches), there's a good chance it has overlapping subproblems and optimal substructure. This is your cue to look for a DP optimization.
  • Can you define a state? The subproblems in DP are often defined by a "state". In the Fibonacci example, the state is simply the index n. In the LIS example, the state was the ending index k. In grid problems, the state is often the coordinate (row, col). Thinking about what minimal information you need to define a subproblem is a huge step toward finding the recurrence relation.

Let's practice this with one more common problem type.

Practice: The Coin Change Problem

Problem: Given a set of coin denominations (e.g., {1, 4, 5}) and a target amount M, find the minimum number of coins required to make that amount.

Let's analyze this for the two DP properties:

  1. Optimal Substructure: Let's say we want to find min_coins(13). If we decide to use a 5-cent coin as one of our coins, our problem reduces to finding 1 + min_coins(8). If we use a 4-cent coin, it's 1 + min_coins(9). The optimal solution for min_coins(13) must be 1 + min(min_coins(12), min_coins(9), min_coins(8)). We can construct the optimal solution for our problem from the optimal solutions of smaller subproblems. This is a clear sign of optimal substructure.

  2. Overlapping Subproblems: Imagine the recursive calls. To solve min_coins(13), we might explore a path that leads to min_coins(8). Another path might start with a different coin but also end up needing the solution for min_coins(8). We would be re-computing this value unnecessarily.

The video from Tech With Nikola explains this exact problem.

Mastering Dynamic Programming - How to solve any interview problem (Part 1)

This segment walks through the Coin Change problem, showing why a simple greedy approach fails and how to formulate a recursive (and thus, DP) solution.

Watch the segment on the Coin Change problem. Notice how the subproblem is defined (minimum_coins(M)) and how the recurrence relation is built by trying every possible first coin.

Conclusion

In this lesson, we laid the conceptual groundwork for Dynamic Programming. You've learned to identify the two essential properties—optimal substructure and overlapping subproblems—that make a problem a candidate for a DP solution. This process of identification and formulating a recurrence relation is the most challenging and creative part of DP.

Key Takeaways:

  • Dynamic Programming is an optimization technique that solves problems by breaking them into smaller, reusable subproblems.
  • Overlapping Subproblems: A property where a naive recursive algorithm computes the same subproblems multiple times. We can spot this by visualizing the recursion tree and seeing repeated branches.
  • Optimal Substructure: A property where the optimal solution to the main problem can be constructed from the optimal solutions to its subproblems. This allows us to create a recurrence relation.
  • Identification Strategy: Look for min/max/count problems, formulate a simple (but slow) recursive solution, and then identify the state that defines your subproblem.

In our next lesson, we will put this theory into practice. We will take the recurrence relations we've learned to identify and implement a top-down DP solution using memoization, which is the direct, practical application of fixing the overlapping subproblems issue.

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

Sign up