Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to the first lesson in our module on "Advanced Algorithmic Patterns."
In our last lesson, we mastered Dijkstra's algorithm, a greedy approach to finding the shortest path in a graph. We saw that its greedy nature—always picking the locally optimal choice—works perfectly for that problem, as long as there are no negative edge weights.
Today, we introduce a different, exceptionally powerful technique for solving optimization problems: Dynamic Programming (DP). While a greedy algorithm commits to a choice at each step, DP systematically explores all options by breaking a problem down into smaller pieces and building up the solution from there. This lesson is foundational. It focuses on the learning outcome: Identify optimal substructure and overlapping subproblems to determine when to apply dynamic programming. By the end, you'll be able to recognize the "signature" of a problem that is a perfect candidate for a DP solution.
1. The Two Hallmarks of Dynamic Programming
Dynamic programming is a method for solving complex problems by breaking them down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions. The next time the same subproblem occurs, instead of recomputing its solution, one simply looks up the previously computed solution.
This strategy is effective only when the problem exhibits two specific properties.
Chapter 16 | Modern Data Structures and Algorithms in Rust
To start, let's get a formal definition of these two properties. The book Modern Data Structures and Algorithms in Rust provides a concise introduction.
Please read the first two paragraphs of section 16.3, 'Dynamic Programming'. Focus on the definitions of overlapping subproblems and optimal substructure.
As the text explains, the two key properties are:
- Overlapping Subproblems: The algorithm needs to solve the same subproblems repeatedly.
- Optimal Substructure: The optimal solution to the overall problem can be constructed from the optimal solutions of its subproblems.
If a problem has these two traits, dynamic programming is almost always a viable and efficient approach. Let's break each one down.
2. Property 1: Overlapping Subproblems
This is the most intuitive property to spot. It means that a naive recursive solution would be inefficient because it does the same work over and over again.
The classic example is calculating the n-th Fibonacci number. The recurrence relation is . A direct recursive implementation would look like this:
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
If we trace the execution for fib(5), we get a recursion tree that reveals a lot of redundant work.

This duplication of effort leads to an exponential time complexity of , which is terribly inefficient. The following video provides an excellent walkthrough of this exact issue and introduces the idea of caching results to fix it.
4 Principle of Optimality - Dynamic Programming introduction
This video by Abdul Bari vividly demonstrates the problem of overlapping subproblems using the Fibonacci sequence and shows how dramatically performance improves by avoiding re-computation.
Watch from 02:34 to 11:31. Focus on: The visualization of the recursive call tree and the repeated function calls (02:34 - 06:43). How storing the results in an array (a technique called memoization) eliminates these redundant calls, reducing the time complexity from exponential to linear (06:43 - 11:31).
The core idea of memoization you just saw—storing the results of expensive function calls and returning the cached result when the same inputs occur again—is a central pattern in dynamic programming. Given your background in building high-performance services like Rudi, you can think of this as a form of application-level caching, where the function's state is the cache key.
3. Property 2: Optimal Substructure
This property is more subtle. It means that you can build the optimal solution to a larger problem by using the optimal solutions of its smaller parts. If we can solve problem(k) for all k < n optimally, we can use those results to figure out the optimal solution for problem(n).
This is what allows us to write a recurrence relation in the first place. For Fibonacci, the solution for Fib(n) is defined by the solutions to Fib(n-1) and Fib(n-2).
Let's explore this with another classic problem: Climbing Stairs.
Dynamic Programming Fundamentals
The article 'Dynamic Programming Fundamentals' on Hello Interview provides a very clear explanation of optimal substructure using the 'Climbing Stairs' problem.
Read the sections 'Optimal Substructure' and 'Overlapping Subproblems'. Notice how 'Climbing Stairs' has the exact same recurrence structure as Fibonacci and therefore exhibits both properties.
As the article explains, the number of ways to reach step n is the sum of the ways to reach step n-1 (and taking one step) and the ways to reach step n-2 (and taking two steps). The optimal solutions to the subproblems (n-1 and n-2) combine to form the optimal solution for the main problem (n).

4. When DP Doesn't Apply: The Absence of Optimal Substructure
To fully appreciate optimal substructure, it's useful to see a problem that lacks it.
In the previous lesson, we saw that Dijkstra's algorithm for the shortest path works because the shortest path problem has optimal substructure. If the shortest path from node A to C goes through B, then the A-to-B part of that path must be the shortest path from A to B.
Now, consider the longest simple path problem (finding the longest path between two nodes in a graph without repeating vertices). Does it have optimal substructure?
Problems Without Optimal Substructure - Dynamic Programming
This video from CSBreakdown explains why the longest path problem is not a candidate for a dynamic programming solution.
Watch from 00:51 to 02:49. Pay close attention to the example where the longest path from A to D, when combined with the path from D to C, does not yield the longest path from A to C. This violates the principle of optimality.
The video demonstrates a critical point: the optimal solution to a subproblem (e.g., longest path from A to D) might use up nodes that are essential for the overall optimal solution (longest path from A to C). The subproblems are not independent in the way required for optimal substructure. This is why there's no simple, efficient algorithm for the longest path problem like there is for the shortest path.
Recognizing this distinction is key to not misapplying DP to problems it cannot solve.
Conclusion
Today, we've built the conceptual framework for Dynamic Programming. You now know the two critical signals to look for when evaluating if a problem is suitable for a DP solution.
Key Takeaways:
- Dynamic Programming is an optimization technique for problems that have Optimal Substructure and Overlapping Subproblems.
- Overlapping Subproblems means a naive recursive solution is inefficient, repeatedly solving the same smaller problems. This is the inefficiency that DP eliminates.
- Optimal Substructure means the optimal solution to a problem can be constructed from the optimal solutions of its subproblems. This property is what allows us to define a recurrence relation.
- Problems like finding the longest path in a graph lack optimal substructure, making them unsuitable for DP or simple greedy algorithms.
Next Lesson Preview
You've learned how to identify a DP problem. In the next lesson, "Solve a classic 1D dynamic programming problem using both top-down memoization and bottom-up tabulation," we'll get our hands dirty. We will take the "Climbing Stairs" problem and implement two different DP solutions in Python: the recursive approach with memoization you saw today, and its iterative counterpart, tabulation.