Dynamic Programming 1. Introduction 2. Memoization 3. Fibonacci and DP 4. Dynamic Programming 4.1. Steps to determine if a problem can be optimized by DP 4.2. DP: interview questions
1. IntroductionDP is an optimization technique using caching.If you have something you can cache, then you can use DP.DP means nothing, it's completely garbage buzzword!DP is a way to solve problems by breaking it down into a collection of sub-problems, solving each of those just once, and storing their solution in case next time the same sub-problem occurs. 2. MemoizationCaching is a way to store values so you can use them later on.Ideally, you can think of caching as a backpack that you take to school. Instead of going all the way home when you need something, you keep them around in your backpack.Caching is just a way for us to speed up our programs and hold some piece of data in an easily accessible box.Memoization is a specific form of caching that involves caching the return value.If the input parameters of the function do not change, then it's memoized (because it uses the cached value).
function addTo80(n) { return n + 80;} let cache = {};function memoizedAddTo80(n){ if (n in cache) { return cache[n]; } else { // this is a simple example but memoization is important when this operation takes a long time to run. cache[n] = n + 80; return cache[n]; }}
There's an improvement we can make to the code above → Ideally, we don't want to fill the cache in the global scope (i.e. living outside the function).Ideally, it's good practice to have memory (or cache) to live inside the function not polluting the global scope.To do so, in JS, we can use closures.
function memoizedAddTo80(){ let cache = {}; return function(n) { if (n in cache) { return cache[n]; } else { cache[n] = n + 80; return cache[n]; } }} const memoized = memoizedAddTo80();console.log(memoized(5));
Note: DP allows us to use memoization to optimize our code. 3. Fibonacci and DPHere's the Fibonacci sequence → 0,1,1,2,3,5,8,13,21,34,55,89,144,233,...With recursion, we are able to calculate the sequence
function fibonacci(n){ if (n < 2) { return n; } else { return fibonacci(n-1) + fibonacci(n-2); }} console.log(fibonacci(5));
How efficient is the above function?It's not very efficient. It's time complexity → O(2n)Also, with recursion we're adding every nested function call to the stack which increases space complexity.With DP, we can do O(n) with this function. 4. Dynamic ProgrammingThis is what our previous Fibonacci function was doing based on recursion only:
Figure 1:Fibonacci with recursion
As you can see, there are so many redundant calculations → For example, it's calculating fib(1) five times in order to get the fib(5) → This is super inefficient.With DP, we only calculate each (repeating function) once, and whenever we need it, we just get the memoized value.You can think of dynamic programming as: DP = Divide & Conquer + Memoization 4.1. Steps to determine if a problem can be optimized by DPIt can be divided into sub-problems.Recursive solution.Are there repetitive sub-problems?Memoize sub-problems?
//0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233...let calculations = 0;function fibonacci(n) { //O(2^n) if (n < 2) { return n } return fibonacci(n-1) + fibonacci(n-2);} function fibonacciMaster() { //O(n) let cache = {}; return function fib(n) { calculations++; if (n in cache) { return cache[n]; } else { if (n < 2) { return n; } else { cache[n] = fib(n-1) + fib(n-2); return cache[n]; } } }} function fibonacciMaster2(n) { let answer = [0,1]; for ( let i = 2; i <= n; i++) { answer.push(answer[i-2]+ answer[i-1]); } return answer.pop();} const fasterFib = fibonacciMaster(); console.log('Slow', fibonacci(35))console.log('DP', fasterFib(100));console.log('DP2', fibonacciMaster2(100));console.log('we did ' + calculations + ' calculations');
4.2. DP: interview questionsHere are some popular Dynamic Programming Questions (see if you can notice the pattern):1. House Robber 2. Best Time to Buy and Sell Stock 3. Climbing Stairs