About DP
In this lesson we will learn about one of the best optimization techniques: Dynamic Programming
The core of this technique is not doing redundant calculations, or in other words, saving already computed values, and using them to build new values that depend on them.
Lets look at a problem to demonstrate how it works:
The Fibonacci numbers are a sequence where each number is the sum of the two preceding ones, starting from 0 and 1. The sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34... Your job is to compute the -th one.
There are 2 main approaches, both are valid and interchangeable, so which one you use comes to personal preference. (of course, for some problems, one might be easier than the other)
Top down dp
The core idea here is to use recursion, along with an additional array which will store already computed values.
So to compute the -th number, we need to sum and .
Similarly, to get , we need and .
We can call these recursively, but in order to avoid calling twice(or more), as soon as we compute one number - lets say , we will save it into memo[n-2]
This process is called memoization!
We also need to define the termination case, in our problem, that case will be , and , as they are already defined in the problem description
Code
int solve(int n, vector<int>& memo){ //very important to pass the reference to memo, and not memo itself
if(n == 0){
return 0;
}
if(n == 1){
return 1;
}
if(memo[n] != -1){ // if we have already computed this value
return memo[n];
}
memo[n] = solve(n-1,memo) + solve(n-2,memo); //compute and save the value
return memo[n];
}We also define memo as: vector<int> memo(n+1,-1)
Time complexity: O(n)
Bottom up dp
The other approach is to start with the smallest value, and then iteratively use it to build bigger values.
For this problem, we would define , and .
Then we would build , etc...
Code
int main(){
int n;
cin>>n;
vector<int> dp(n+1);
dp[0] = 0;
dp[1] = 1;
for(int i=2;i<=n;i++){
dp[i] = dp[i-1] + dp[i-2];
}
cout<<dp[n];
}Time Complexity: O(n);
Verdict:
Whilst both approaches do have the same time complexity, bottom up is generally preferred as the constant factors are smaller, and it is simpler to implement(it also doesn't need recursion which can be unreliable at times)
Note:
Whilst dp is a powerful concept, most dp problems are just a reskin of a few elementary ones, in the next few lessons, we will cover the elementary dp problems.