Dynamic Programming Introduction - Tutorial (Updated)
Dynamic Programming Introduction - Tutorial (Updated)
Name*
Note: The base case does not always mean smaller input. It depends upon the implementation of the algorithm.
Java Operators
C++ Functions
We will be using the example of Fibonacci numbers here. The following series is called the Fibonacci series: Amazon
Arcesium
Bank of America
Barclays
BFS
Accolite Digital
Binary Search
Binary Search Tree
Commvault
CPP
DE Shaw
0,1,1,2,3,5,8,13,21,…
DFS
DSA Self Paced
google
HackerEarth
We need to find the nth Fibonacci number, where n is based on a 0-based index.
infosys
inorder
Java
Juspay
Kreeti Technologies
Morgan Stanley
Every ith number of the series is equal to the sum of (i-1)th and (i-2)th number where the first and second number is given Newfold Digital
Oracle
post order
pre-order
queue
recursion
Samsung
Disclaimer: Don’t jump directly to the solution, try it out yourself first.
CODEVITA
TCS DIGITA;
TCS Ninja
TCS NQT
VMware
Pre-req: Recursion XOR
Solution :
Part – 1: Memoizaton
As every number is equal to the sum of the previous two terms, the recurrence relation can be written as:
We want to compute f(2) as the second call from f(4), but in the recursive tree we had already computed f(2) once (in the
first recursive call of f(3) ) Similar is the case with f(3), therefore if we somehow store these values, the first time we
calculated it then we can simply find its value in constant time whenever we need it. This technique is called Memoization.
Here the cases marked in yellow are called overlapping sub-problems and we need to solve them only once during the code
execution.
Any recursive solution to a problem can be memoized using these three steps:
Dry Run:
Now, following the recursive code we see that at n=5, the value of dp[5] is equal to -1 therefore we need to compute its
value and go to the inner recursive calls. Similar is the case at n=4, n=3, and n=2. For n=2, we hit our two base conditions as
the inner recursive calls.
As we traverse back after solving n=2, we update its dp array value to 1 ( the answer we got). Similarly, for the second
recursive call for n=3, we again hit a base condition and we get an answer of f(n=3) as 2, we again update the dp array.
Then for the second recursive call f(n=4), we see that dp[2] is not equal to -1, which means that we have already solved
this subproblem and we simply return the value at dp[2] as our answer. Hence we get the answer of f(n=4) as 3(2+1).
Similarly, for the second recursive call f(n=5), we get dp[3] as 2. Then we compute for(n=5) as 5(2+3).
Code:
C++ Code
#include <bits/stdc++.h>
if(n<=1) return n;
int main() {
int n=5;
vector<int> dp(n+1,-1);
cout<<f(n,dp);
return 0;
Output: 5
Reason: The overlapping subproblems will return the answer in constant time O(1). Therefore the total number of new
subproblems we solve is ‘n’. Hence total time complexity is O(N).
Reason: We are using a recursion stack space(O(N)) and an array (again O(N)). Therefore total space complexity will be O(N)
+ O(N) ≈ O(N)
Java Code
import java.util.*;
class TUF{
if(n<=1) return n;
int n=5;
Arrays.fill(dp,-1);
System.out.println(f(n,dp));
Output: 5
Reason: The overlapping subproblems will return the answer in constant time O(1). Therefore the total number of new
subproblems we solve is ‘n’. Hence total time complexity is O(N).
Reason: We are using a recursion stack space(O(N)) and an array (again O(N)). Therefore total space complexity will be O(N)
+ O(N) ≈ O(N)
Tabulation is a ‘bottom-up’ approach where we start from the base case and reach the final answer that we want.
Steps to convert Recursive Solution to Tabulation one.
Code:
C++ Code
#include <bits/stdc++.h>
int main() {
int n=5;
vector<int> dp(n+1,-1);
dp[0]= 0;
dp[1]= 1;
cout<<dp[n];
return 0;
Output: 5
Java Code
import java.util.*;
class TUF{
int n=5;
Arrays.fill(dp,-1);
dp[0]= 0;
dp[1]= 1;
System.out.println(dp[n]);
Output: 5
we see that for any i, we do need only the last two values in the array. So is there a need to maintain a whole array for it?
The answer is ‘No’. Let us call dp[i-1] as prev and dp[i-2] as prev2. Now understand the following illustration.
Each iteration’s cur_i and prev becomes the next iteration’s prev and prev2 respectively.
Therefore after calculating cur_i, if we update prev and prev2 according to the next step, we will always get the answer.
After the iterative loop has ended we can simply return prev as our answer.
Code:
C++ Code
#include <bits/stdc++.h>
int main() {
int n=5;
int prev2 = 0;
int prev = 1;
prev2 = prev;
prev= cur_i;
cout<<prev;
return 0;
Output: 5
Java Code
import java.util.*;
class TUF{
int n=5;
int prev2 = 0;
int prev = 1;
prev2 = prev;
prev= cur_i;
System.out.println(prev);
Output: 5
Special thanks to Anshuman Sharma for contributing to this article on takeUforward. If you also wish to share your
knowledge with the takeUforward fam, please check out this article
Load Comments