Dynamic Programming - Introduction
What is Dynamic Programming?
- Dynamic Programming or DP is a method for solving a problem by breaking it into smaller sub-problems, solving the subproblems only once and storing the result of the sub-problems for future use so that we do not solve a sub-problem again and again (like we do in Divide and Conquer).
- Dynamic Programming is just an optimization of Divide and Conquer. In Divide and Conquer we may solve sub-problem multiple times, but DP prevents that by storing the result of a sub-problem once it is solved once, thus saving on run-time and compromising a little on space complexity.
When Should we use the Dynamic Programming approach?
When we see these both of these properties in a problem, we can use DP:
- Optimal Substructure
- This property has already been explained in the post of Divide and Conquer, as Divide and Conquer also have this property.
- Overlapping Subproblems
- Any problem has overlapping sub-problems if finding its solution involves solving the same sub-problems multiple times.
- This property is unique for Dynamic Problem.
Two Types of Dynamic Programming Approach
1. Top-Down Approach (Memoization)
- In this approach of Dynamic Problem, the problem is first broken into sub-problems. Then these sub-problems are solved.
- The solution to these sub-problems is remembered so that we can use it in the future if needed.
- This is simply the extension of Dynamic Programming.
- Example:
1. We break fib(4) = fib(3) + fib(2)
2. Then we break fib(3) = fib(2) + fib(1) whose value we know as 1 and 0 respectively.
Here, We know the values of fib(1) = 0 and fib(2) = 1 [base cases]. We have already got it stored somewhere. So, when we get to the recursive call of fib(3) it picks the value of fib(1) and fib(2) directly add it and return the value of fib(3).
3. Now. we reach step 2 again (by recursion). Here instead of now calling fib(2) again, we use its value from memory and return the final value of fib(4).
So, what we are doing here is we are first breaking a given problem into smaller subproblems, solving individual sub-problems once and storing its result, and then combining result of these subproblems to get the solution of bigger subproblem and eventually get the solution to the whole problem.
2. Bottom-Up Approach (Tabulation)
- In this approach, instead of breaking the problem into sub-problems recursively, we evaluate the function starting with the smallest possible input argument value and then moving to the larger input value.
- Here we go from solving the smallest problem possible to eventually solving the main problem. But remember, we DO NOT BREAK A PROBLEM INTO SUBPROBLEMS
- While computing the values we store all the computed values in memory(table).
- As larger input arguments are evaluated, pre-computed values for smaller arguments can be reused.
- In this case, all the subproblems that we might need in future are precompiled and then they are used to solve bigger problems.
- Example:
1. We first try to find fib(1), which we already store in our memory as base case along with fib(2) which is 1.
2. Now, we go on to find fib(2) => which we get from our memory
3. Now, we go on to find fib(3) = fib(2) + fib(1), which we find using already stored values of fib(2) and fib(1) and adding them. We then save the result of fib(3) in memory.
4. Now, we go on to find fib(4) = fib(3) + fib(2), both of which can be found in our memory, so no computation needed.
So, what we are doing here is we are starting from solving smallest possible chunk and eventually go on to solve bigger chunks using these pre-computed values of smaller chunks.
Comparing Fibonacci Number problem's Time complexity
Problem | Divide and Conquer | Top-Down DP | Bottom-Up DP |
Fibonacci Number | Exponential (O(1.61)^n) | O(n) | O(n) |
Top-Down Approach vs Bottom-Up Approach
Comparision Factor | Top-Down Approach | Bottom-Up Approach |
Ease of Algorithm | Easy to come up with a solution as it is an extension of Divide and Conquer. | Not so easy to come up with a solution. |
Run-Time | Relatively slower as it is an Recursive approach. | Relatively faster as it is an Iterative approach. |
Space | The internal stack is used because of recursion | No internal stack is used as it is iterative. |
When to use | When a quick solution is needed | When a more efficient solution is needed |
Famous Problems (relevant to coding interview) that can be solved using Dynamic Programming
Important Note: I would highly recommend solving these problems. These are common and are mostly asked problems, which will also help us to gain an understanding of Dynamic Programming. To really understand Dynamic Programming, we should emphasize more on practicing its questions. For now, we can start with these:
1. Fibonacci Series
2. Number Factors / Coin Change Problem
3. House Robber
4. Minimum Edit Distance problem
5. Longest Common Subsequence
6. Longest Palindromic Subsequence
7. Longest Palindromic Substring
8. Min Cost to reach the last cell (bottom-right) from the first cell (top-left) in a 2D array
9. Number of possible paths from the first cell (top-left) to the last cell (bottom-right) in a 2D array
Comments
Post a Comment