Skip to main content

Welcome to a Grasp lesson

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

Lesson 1

Identifying DP Problems

Recommended resources

Welcome to your first lesson on dynamic programming. This course is designed to build a strong, first-principles understanding of DP, moving from core concepts to the practical synthesis of solutions. Our goal today is to establish the fundamental conditions that make a problem suitable for a dynamic programming approach. By the end of this lesson, you will be able to characterize problems solvable by DP by identifying two key properties: optimal substructure and overlapping subproblems.

This foundational understanding is the "why" behind DP. It explains what structural properties in a problem signal that DP might be the right tool, distinguishing it from other recursive approaches like simple divide-and-conquer.

The Two Hallmarks of a DP Problem

Dynamic programming is fundamentally an optimization technique for a specific class of recursive problems. A naive recursive solution to these problems is often correct but inefficient, typically running in exponential time. DP provides a systematic way to optimize this to polynomial time. This optimization is possible only when the problem exhibits two specific properties.

Let's examine them one by one.

1. Overlapping Subproblems

Consider the naive recursive computation of Fibonacci numbers: . If we trace the execution for , we see that we compute twice, three times, and so on. The same subproblems are solved again and again. This property is called overlapping subproblems.

A graphical introduction to dynamic programming

This article provides an excellent visual demonstration of overlapping subproblems using the Fibonacci sequence. It contrasts the naive recursive call tree with a memoized version, which avoids recomputing results.

Please read the introduction and the first two sections, Fibonacci numbers and Using memoization. Pay close attention to the call tree diagrams showing the redundant computations. The article then introduces the core idea of memoization (caching) to solve this inefficiency. Finally, read the section on the bottom-up approach and observe the diagram that collapses the call tree into a subproblem dependency graph.

The key insight is that the number of unique subproblems is small (for Fibonacci, it's just subproblems, ), while the number of calls in the naive recursion is exponential. Dynamic programming, through a technique called memoization (a form of caching), stores the solution to each subproblem after computing it once. Subsequent calls that need the solution to that same subproblem simply look it up, eliminating the redundant work.

As the MIT OpenCourseWare lecture on dynamic programming explains, this simple idea is what turns an exponential-time algorithm into a polynomial-time one.

15. Dynamic Programming, Part 1: SRTBOT, Fib, DAGs, Bowling

This segment of the lecture uses the Fibonacci example to perfectly illustrate the cost of overlapping subproblems and the power of memoization.

Watch the segment from the introduction of Fibonacci to the explanation of the recursion tree. Notice how the lecturer identifies identical subproblems being computed multiple times. Then, continue watching from this discussion of how memoization—remembering and reusing solutions—collapses this redundant work. The analysis at the end connecting runtime to the number of subproblems is crucial: Time ≈ (# subproblems) * (work per subproblem).

In contrast, an algorithm like Mergesort also breaks a problem into subproblems. However, when sorting an array, the two halves it recursively sorts are disjoint. No subproblem is ever solved more than once. This is characteristic of divide-and-conquer algorithms, which are efficient without memoization because their subproblems do not overlap.

2. Optimal Substructure

The second property, optimal substructure, means that the optimal solution to the overall problem can be constructed from the optimal solutions to its subproblems.

This might sound tautological, but it's a specific structural property that not all problems possess. The classic example of optimal substructure is the shortest path problem. If the shortest path from node A to node C passes through node B, then the A-to-B portion of that path must be the shortest path from A to B. If it weren't, you could substitute the true shortest A-to-B path and get an even shorter A-to-C path, which is a contradiction.

This graph illustrates optimal substructure. The shortest path from start to goal (cost 25) is composed of the shortest path from start to the intermediate node (cost 5) and the path from there to the goal (cost 20). The total optimal solution is built from an optimal sub-solution.

A problem that lacks optimal substructure is finding the longest simple path (no repeated vertices) in an unweighted cyclic graph. Suppose the longest simple path from A to C goes through B. The subpath from A to B is not necessarily the longest simple path from A to B, because the globally optimal path might use nodes that a locally optimal path would need to "save" for the B-to-C segment.

For DP problems, optimal substructure is typically expressed via a recurrence relation. This relation defines how to compute the solution for a given problem from the solutions of its subproblems.

For example, in the "House Robber" problem, where you cannot rob adjacent houses, we can define f(i) as the maximum value obtainable from houses 0 to i. The recurrence relation is:

This expresses the two choices at house i:

  1. Rob house i: Gain its value , but then you can only add the optimal solution from houses up to i-2.
  2. Skip house i: Gain nothing now, but you are free to take the optimal solution from houses up to i-1.

The optimal solution f(i) is constructed by taking the maximum of two options, each of which relies on the pre-computed optimal solutions to smaller subproblems (f(i-1) and f(i-2)).

Solving DP problems by SRTBOT Framework - MartinLwx's Blog

This article gives concise, direct definitions for our two key concepts.

Read the section What's the DP problem?. This reinforces the definitions of both properties and the distinction from divide-and-conquer.


Synthesis: The Subproblem Dependency Graph

The most powerful mental model for dynamic programming, especially given your background, is to view the problem as a Directed Acyclic Graph (DAG) of subproblem dependencies.

  • Vertices are the unique subproblems.
  • Directed Edges represent dependencies. An edge from subproblem u to v (u -> v) means that the solution to v depends on the solution to u.

The two properties map perfectly to this model:

  1. Overlapping Subproblems: A vertex in the DAG with an in-degree greater than one represents an overlapping subproblem. It's a dependency for multiple other subproblems.
  2. Optimal Substructure: The edges themselves, which are defined by the recurrence relation, represent the principle of optimal substructure. They show how larger problems are constructed from smaller ones.

The graph must be acyclic; otherwise, a subproblem would depend on itself, leading to an infinite recursion.

This is a DAG for a compiler's intermediate representation. The nodes are operations/values, and edges are dependencies. DP subproblem graphs have an identical structure: nodes are subproblems, and edges are dependencies defined by the recurrence. Solving a DP problem is analogous to evaluating this graph in topological order.

The process of solving a DP problem "bottom-up" is equivalent to processing the nodes of this DAG in a topological order. You start with the base cases (nodes with an in-degree of 0) and compute the values for subsequent nodes, ensuring that by the time you evaluate any node v, all its dependencies (nodes u for which an edge u -> v exists) have already been computed. This is directly analogous to how a compiler might evaluate dependencies in an expression tree or an instruction scheduler might order operations.

The following video provides a fantastic walkthrough of this DAG-centric view for two problems: Longest Increasing Subsequence and Box Stacking.

5 Simple Steps for Solving Dynamic Programming Problems

This video excels at visualizing DP problems as graphs. It makes the abstract concepts of subproblems and dependencies concrete.

First, watch the section on the Longest Increasing Subsequence (LIS), from the problem definition to the explanation of the DAG. See how an increasing subsequence is mapped to a path in the graph. The key idea is that the problem itself can be reframed as a longest-path problem on a DAG. Next, skip to the Box Stacking problem. Watch how it's also visualized as a DAG, starting at the visualization section. Here, a valid stack of boxes is a path in the graph. The subproblem is defined as the tallest stack with a specific box at the base, which corresponds to finding the longest path starting from a particular node.

This framing of DP as finding optimal paths on an implicit DAG is a recurring and powerful pattern. We even saw that the standard algorithm for single-source shortest paths on a DAG is itself a dynamic program. The recurrence is simply the relaxation step, performed in topological order.


Summary

To determine if a problem is a candidate for dynamic programming, you must verify it has both:

  1. Overlapping Subproblems: A recursive solution solves the same subproblems multiple times. This signals an opportunity for optimization via memoization.
  2. Optimal Substructure: The optimal solution to the overall problem can be constructed from the optimal solutions of its subproblems. This allows you to define a recurrence relation.

Together, these properties imply that the problem can be modeled as finding an optimal value over a subproblem dependency DAG. The DP algorithm is then simply a traversal of this DAG that computes the value for each node (subproblem) based on the values of its predecessors, guaranteeing that each subproblem is solved only once.

In our next lesson, we will move from identifying these properties to the first step of implementation: modeling a problem by defining its state representation and recurrence relation.