Introduction to Dynamic Programming
Table of Contents + β
Some problems feel slow no matter how you write the code. You give the computer a small input and it answers fast. You give it a slightly bigger input and it just sits there. The work explodes. Most of the time the reason is simple. Your code keeps solving the same small piece again and again. Dynamic programming is the trick that fixes exactly this.
π€ What Is Dynamic Programming?
Dynamic programming is a way to solve a big problem. You break it into smaller problems. You solve each small one only once. Then you save that answer so you never have to compute it again.
That last part is the whole point. Solve once. Store the answer. Reuse it later. The small problems are called subproblems. A subproblem is just the same kind of question on a smaller input.
Think of it like homework. Say one question depends on the answer to an earlier question. A smart student writes the earlier answer down on the side. Then when it comes up again, they just read it off the paper. They do not redo the work. Dynamic programming makes the computer do the same thing.
π© The Pain: Plain Recursion for Fibonacci
Let us look at the classic example. The Fibonacci numbers. Each number is the sum of the two before it. So it goes 0, 1, 1, 2, 3, 5, 8, 13, and so on.
The math rule is short. fib(n) = fib(n-1) + fib(n-2). The first two values are fixed. fib(0) = 0 and fib(1) = 1.
This rule turns into recursion very naturally. A function that calls itself for the two smaller cases.
# plain recursion: recomputes the same values again and againdef fib(n): if n < 2: # fib(0)=0, fib(1)=1 return n return fib(n - 1) + fib(n - 2)
print("fib(10) =", fib(10))The output of the above code will be:
fib(10) = 55This gives the right answer. The problem is how it gets there. Watch what happens when you ask for fib(5).
See fib(3) showing up twice? And fib(2) shows up over and over. Each one re-opens its own little tree of work below it. Nobody is writing the answer down. So the same small questions get solved again and again.
This is why it explodes. Here is the rough cost as n grows.
Caution
Plain Fibonacci recursion runs in about O(2^n) time. For fib(50) that is around a trillion calls. Your program will look frozen.
π Overlapping Subproblems
The repeated work above has a name. Overlapping subproblems means the same smaller problem comes up many times while you solve the big one.
That is the first signal for dynamic programming. If you ever notice your recursion asking the same question more than once, you have overlapping subproblems.
So what does DP do about it? It is simple.
- Solve each subproblem one time.
- Store its answer in a table or an array.
- The next time that same question comes up, read the stored answer instead of computing it again.
For Fibonacci, that turns all those repeated branches into a single quick lookup. The tree of work collapses into a straight line.
π§± Optimal Substructure
There is another signal too. Optimal substructure means the best answer to the big problem is built from the best answers to its smaller problems.
For Fibonacci this is clearly true. fib(n) is just fib(n-1) plus fib(n-2). If you have the correct answers to those two smaller pieces, you have the correct answer to the big one. Nothing else is needed.
When both signals show up together, DP fits the problem.
- Overlapping subproblems means storing answers will actually save work.
- Optimal substructure means you can build the big answer from smaller stored answers.
Tip
Quick test before you reach for DP. Does the problem repeat the same smaller question many times? And can you build the full answer from smaller answers? If both answers are yes, DP is a good fit.
β‘ Before and After on Fibonacci
The fix for Fibonacci is small. Keep an array. Each spot holds one Fibonacci value. Fill them in order, from small to big. Every value gets computed once. After that you just read it when you need it.
Here is the same Fibonacci, but now each value is stored and reused.
# store each value once, then reuse itdef fib(n): if n < 2: # handles fib(0) and fib(1) return n dp = [0] * (n + 1) dp[1] = 1 for i in range(2, n + 1): dp[i] = dp[i - 1] + dp[i - 2] # reuse the two stored answers return dp[n]
print("fib(10) =", fib(10))The output of the above code will be:
fib(10) = 55Same answer, 55. But now look at the cost. The loop runs once for each value up to n. No value is ever computed twice.
Output
Same problem, two very different speeds:
- Plain recursion: about O(2^n) time.
fib(50)is roughly a trillion calls. - Stored DP version: O(n) time.
fib(50)is just 50 simple steps.
Here is the difference side by side.
| Approach | Repeats work? | Time | Space |
|---|---|---|---|
| Plain recursion | Yes, a lot | O(2^n) | O(n) |
| DP with stored answers | No | O(n) | O(n) |
That jump from O(2^n) to O(n) is the whole reason dynamic programming exists. You stopped redoing work.
β οΈ Common Mistakes
A few things trip people up early on. Watch for these.
- Reaching for DP when there is no repeated work. If each subproblem is different, storing answers buys you nothing.
- Forgetting the base cases. For Fibonacci that is
fib(0)andfib(1). Without them the recursion never stops. - Filling the table in the wrong order. A spot must be filled only after the smaller spots it depends on are already filled.
- Thinking DP is one fixed algorithm. It is not. It is just the idea of solving each subproblem once and storing it.
π§© What Youβve Learned
- β Dynamic programming solves a big problem by breaking it into smaller subproblems, solving each once, and storing the answer.
- β Plain recursion for Fibonacci is slow because it recomputes the same values again and again, costing about O(2^n).
- β Overlapping subproblems means the same smaller question comes up many times, so storing answers saves real work.
- β Optimal substructure means the best big answer is built from the best smaller answers.
- β Storing answers turns Fibonacci from O(2^n) down to O(n). Same result, far less work.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What is the core idea of dynamic programming?
Why: DP breaks a problem into subproblems, solves each one time, and stores the answer so it is never recomputed.
- 2
Why is plain recursive Fibonacci so slow for larger n?
Why: The same calls like fib(3) and fib(2) repeat across the tree, pushing the cost to about O(2^n).
- 3
What does 'overlapping subproblems' mean?
Why: Overlapping subproblems means the same smaller question appears repeatedly, so storing its answer saves work.
- 4
What is the time complexity of Fibonacci once each value is stored and reused?
Why: With each value computed once in a single pass up to n, the work is O(n).