Dynamic programming a technique for solving recursive style problems. Put simply, dynamic programming is basically just using caches to improve recursive algorithms.
Letโs explain with an example: the Fibonacci sequence. Using a brute-force implemtation like so:
function fib(num) {
if (num < 2) return num;
else return fib(n - 2) + fib(n - 1);
}
However, this is not particularly efficient, with a computation time of . We can see that from this graph:
flowchart TB
7["fib(7)"] --- 6["fib(6)"]
7 --- 5["fib(5)"]
6 --- 5a["fib(5)"]
6 --- 4a["fib(4)"]
5 --- 4["fib(4)"]
5 --- 3["fib(3)"]
3 --- 2["fib(2)"]
3 --- 1["fib(1)"]
2 --- 0["fib(0)"]
2 --- 1a["fib(1)"]
4 --- 3a["fib(3)"]
4 --- 2b["fib(2)"]
2b --- 0x["fib(0)"]
2b --- 1x["fib(1)"]
3a --- 2c["fib(2)"]
3a --- 1c["fib(1)"]
2c --- 0v["fib(0)"]
2c --- 1v["fib(1)"]
5a --- 4m["fib(4)"]
5a --- 3m["fib(3)"]
3m --- 2m["fib(2)"]
3m --- 1m["fib(1)"]
2m --- 0m["fib(0)"]
2m --- 1am["fib(1)"]
4m --- 3am["fib(3)"]
4m --- 2bm["fib(2)"]
2bm --- 0xm["fib(0)"]
2bm --- 1xm["fib(1)"]
3am --- 2cm["fib(2)"]
3am --- 1cm["fib(1)"]
2cm --- 0vm["fib(0)"]
2cm --- 1vm["fib(1)"]
4a --- 3at["fib(3)"]
4a --- 2bt["fib(2)"]
2bt --- 0xt["fib(0)"]
2bt --- 1xt["fib(1)"]
3at --- 2ct["fib(2)"]
3at --- 1ct["fib(1)"]
2ct --- 0vt["fib(0)"]
2ct --- 1vt["fib(1)"]
Thatโs quite a bit of function calls. But, youโll see that we call fib
many times with the same arguments. Obviously, that would be much better if we could cache it.
Thinking in dynamic programming
Letโs backtrack all the way to the start. We need to find the n
th Fibonacci number. In dynamic programming, we break up the problem into pieces. So, in order to find the n
th Fibonacci number, we add the n - 1
th and the n - 2
th. Now we have two pieces. Then three, etcโฆ Weโll notice that there are many pieces that are repeated. We should cache this.
Another example is simple arithmetic. Say we have: Letโs rearrange this: Merge it furthermore: As you can see, we recognize that we have to evaluate some patterns multiple times. We can combine them to make a more efficient way.
Back to Fibonacci
Now, how should we create our dynamic programming Fibonacci function? Thereโs two main ways, memoization and tabulation. If implemented properly (either way), the call stack should look like so1:
flowchart TB
7["fib(7)"]
6["fib(6)"]
5["fib(5)"]
4["fib(4)"]
3["fib(3)"]
2["fib(2)"]
1["fib(1)"]
0["fib(0)"]
7 --- 6
7 --- 5
6 --- 5
6 --- 4
5 --- 4
5 --- 3
4 --- 3
4 --- 2
3 --- 2
3 --- 1
2 --- 1
2 --- 0
Much better. This only has a computation time of
Footnotes
-
WHY do the lines from the left to the right look like that. Stupid. I canโt figure out how to make it not though. โฉ