Dynamic Programming Part I
December 28th, 2024
In Dynamic Programming (DP), we use cache not only to eliminate redundant computations, but to determine the answer for new inputs. As with any algorithmic pattern, the real test of understanding is by being able to identify the conditions when employing the pattern will achieve the optimal solution -- not by a rote memorization of the pattern itself.
Properties of a Dynamic Programing Problem
- The answer is known for a subset of inputs
If the answer is trivial for a known subset of inputs, the following question should be asked:
Given a cache with answers for input set[x:z]
, can we determine the answer for inputx1
?
If so, this is a signal that a dynamic cache may be a sound answer to our problem.
- The solution is expressed in terms of itself
This property requires that the answer to the problem is defined as the answer to the same problem with different inputs.
Two of the most common examples of this algorithmic pattern, the fibonacci sequence and climbing stairs, are perfect illustrations of both properties.
The Fibonacci Sequence
What is the nth number in the Fibonacci sequence?
Fibonacci sequence: each term (after the first two) is the sum of the previous two terms.
123456789cache = {}
def fibonacci(n: int) -> int:
if n in cache:
return cache[n]
if n <= 1:
cache[n] = n # (property 1)
return n
cache[n] = fibonacci(n - 1) + fibonacci(n - 2) # property (2)
return cache[n]
Climbing Stairs
Assuming you can only climb either 1 or 2 steps at a time, how many different ways can you climb a staircase of N steps?
n = 3: [1,1,1], [1,2], [2,1]
123456789cache = {}
def climb_stairs(n: int) -> int:
if n in cache:
return cache[n]
if n <= 3:
cache[n] = n # property (1)
return n
cache[n] = climb_stairs(n - 1) + climb_stairs(n - 2) # property (2)
return cache[n]
In each of the above examples, we can see the properties above very clearly.
First, we know the answer for a trivial subset of inputs; for
fibonacci
, we know the answer for all inputs less than or equal to 1; for climb_stairs
, we know the answer to all inputs less than or equal to 3. Coming to this conclusion requires little more than observation. It does not require much thought.Second, we express the solution in terms of itself; for
fibonacci
, this only requires observation of the mathematical definition of the sequence of numbers; for climbing_stairs
, it requires a bit of logical reflection. To climb n
stairs, we determine all unique permutations of 1 and 2 which sum to n
by observing the answer will be the same as the subset of permutations of n - 1
-- where every individual permutation has a 1 step
appended to it -- and of n - 2
-- where every individual permutation has a 2 step
appended to it.Conclusion
These two problems are in essence one. The only difference is that property (2) is given to us by the formal definition of the
fibonacci sequence
whereas in climbing stairs, we have to detect this property of the solution ourselves.In Part II, we examine two variations of the Coin Change problem. This will serve as an introduction to the need for a more complex shape for our cache.
Please let me know if you found this article useful or if you would like to provide further clarity and corrections by sending me an email at maxwellnkendall@gmail.com.