├── Fibonacci.cpp ├── Climbing_stairs.cpp ├── Longest Increasing Subsequence.cpp ├── Dice Combinations.cpp └── README.md /Fibonacci.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | class Solution 6 | { 7 | int dp[31]; 8 | 9 | public: 10 | int func(int n) 11 | { 12 | if (n < 2) 13 | return n; // base case 14 | if (dp[n] != -1) 15 | return dp[n]; // if already calculated 16 | return dp[n] = func(n - 1) + func(n - 2); // recursive case 17 | } 18 | int fib(int n) 19 | { 20 | memset(dp, -1, sizeof(dp)); // initialize dp array with -1 21 | return func(n); 22 | } 23 | }; 24 | 25 | int main() 26 | { 27 | Solution ob; 28 | int n; 29 | cin >> n; 30 | cout << ob.fib(n); 31 | } -------------------------------------------------------------------------------- /Climbing_stairs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | class Solution 5 | { 6 | int dp[46]; 7 | 8 | public: 9 | int func(int n) 10 | { 11 | if (n < 0) 12 | return 0; // base case 13 | if (n == 0) 14 | return 1; 15 | if (dp[n] != -1) 16 | return dp[n]; // if already calculated 17 | return dp[n] = func(n - 1) + func(n - 2); // recursive case 18 | } 19 | int climbStairs(int n) 20 | { 21 | memset(dp, -1, sizeof(dp)); // initialize dp array with -1 22 | return func(n); 23 | } 24 | }; 25 | 26 | int main() 27 | { 28 | Solution ob; 29 | int n; 30 | cin >> n; 31 | cout << ob.climbStairs(n); 32 | } -------------------------------------------------------------------------------- /Longest Increasing Subsequence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | int solve(int ind, int prev_ind, int arr[], int n, vector> &dp) { 5 | if(ind == n) return 0; // BASE CASE 6 | 7 | // MEMOIZATION 8 | if(dp[ind][prev_ind+1] != -1) return dp[ind][prev_ind+1]; 9 | 10 | 11 | int len = 0 + solve(ind+1, prev_ind, arr, n, dp); // NOT TAKING CASE 12 | 13 | 14 | if(prev_ind == -1 || arr[ind] > arr[prev_ind]) { //TAKE CASE 15 | len = max(len , 1 + solve(ind+1, ind, arr, n, dp)); 16 | } 17 | return dp[ind][prev_ind + 1] = len; 18 | } 19 | 20 | int main() 21 | { 22 | int n; 23 | cin >> n; 24 | int arr[n]; 25 | for(int i=0;i>arr[i]; 27 | } 28 | 29 | vector> dp(n, vector(n+1, -1)); 30 | int ans = solve(0, -1, arr, n, dp); 31 | cout << ans << endl; 32 | } 33 | -------------------------------------------------------------------------------- /Dice Combinations.cpp: -------------------------------------------------------------------------------- 1 | //Problem Statement -> Your task is to count the number of ways to construct sum n by throwing a dice one or more times. 2 | //Each throw produces an outcome between 1 and 6. 3 | 4 | #include 5 | using namespace std; 6 | 7 | // Let's define the four important things before diving into the solution 8 | // 1) State -> dp[i] where it signifies the number of ways to construct sum i 9 | // 2) Transition -> dp[i] = dp[i] + dp[i-1] + dp[i-2] + ... + dp[i-6] 10 | // 3) Base case -> dp[i<0] = 0 && dp[0] = 1 11 | // 4) Final subproblem -> dp[n] (number of ways to construct sum n) 12 | 13 | //Time complexity -> O(n) 14 | 15 | const int N = 1000010; 16 | const int M = 1000000007; 17 | 18 | int dp[N]; 19 | 20 | int numberOfWays(int i){ 21 | 22 | //Base cases 23 | if (i < 0) return 0; // if i is negative, there is no way to construct given sum because a dice throw caannot be negative 24 | if (i == 0) return 1; //if i is 0, there is only one way to construct sum 0 which is not throwing dice at all 25 | 26 | //Memoisation 27 | if (dp[i] != -1) return dp[i]; //if we have already calculated the number of ways to construct sum i, we will use the stored ans instead of computing it again 28 | 29 | dp[i] = 0; //initializing number of ways to construct sum i to 0 30 | for (int j = 1; j <= 6; j++){ //for loop to iterate 6 times for each possible dice throw 31 | dp[i] = (dp[i] + numberOfWays(i-j))%M; //transition 32 | // we're supposed to print the answer modulo 10^9+7 33 | } 34 | 35 | return dp[i]; //returning the number of ways to construct sum i which will be stored in dp[i] 36 | } 37 | 38 | void solve(){ 39 | int n; 40 | cin >> n; //the sum we need to construct 41 | 42 | memset(dp, -1, sizeof(dp)); //intializing dp array with -1 43 | cout << numberOfWays(n) << endl; //printing the final subproblem 44 | 45 | } 46 | 47 | int main() { 48 | solve(); 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

⭐ DYNAMIC PROGRAMMING ⭐

5 | 6 |

Dynamic Programming is mainly an optimization over plain recursion. Wherever we see a recursive solution that has repeated calls for same inputs, we can optimize it using Dynamic Programming. The idea is to simply store the results of subproblems, so that we do not have to re-compute them when needed later. This simple optimization reduces time complexities from exponential to polynomial.

7 | 8 | --- 9 | 10 | 11 |
12 |

Why Dynamic Programming

13 | ● Overlapping Subproblems
14 | ● Why calculate the answers for the subproblems again and again
15 | ● DP helps covers all possible cases
16 |
17 | 18 |

Greedy V/S DP

19 |

Greedy (one direction)

20 |

Pick k elements from a list of N elements such that their sum is maximum.

21 | 22 |

DP (try out all directions)

23 |

Pick k elements from a list of n elements such that a particular condition holds. ((f..........))
24 | Time complexity
25 | 1. For trying out every possibility - C(n,k) => nCk (brute force)
26 | 2. With DP (break problem into sub-problems and also storing subproblems) - O(n*k) => dp[n][k]

27 | 28 | --- 29 | 30 |

Basic DP question

31 |

Find the sum of first n natural numbers. Without using the formula (N * (N + 1 )) / 2

32 |

Basic solution

33 |
 34 | int sum = 0;
 35 | for (int i = 1; i <= n; i++)
 36 | {
 37 |     sum += i;
 38 | }
 39 | cout << sum;
 40 | 
41 |

sum variable denotes the sum of n natural number

42 | 43 |

DP solution

44 |
 45 | int dp[n + 1];
 46 | for (int i = 1; i <= n; i++)
 47 | {
 48 |     dp[i] = i + dp[i - 1];
 49 | }
 50 | cout << dp[n];
 51 | 
52 |

Here, dp[i] signify sum till i

53 |
54 | 55 | --- 56 | 57 |

4 things to care about in DP problems ✨

58 | 59 |

1. State

60 |

A subproblem that we want to solve. The subproblem may be complex or easy to solve but the final aim is to solve the final problem which may be defined by a relation between the smaller sub problems 61 | (representation of a subproblem -> what does it signify)

62 | 63 |

2. Transition

64 |

Calculate the answer of state by using the answer of other smaller state (subproblems)

65 | 66 |

3. Base case

67 |

The transition expression will itself give the base case (points where the transition fails)

68 | 69 |

4. Final subproblem

70 |

Final state that the answer lies in

71 | 72 | --- 73 | 74 |

Fibonacci Number

75 |

Find the Nth Fibonacci Number where F(n) = F(n-1) + F(n-2)
76 | F(1) = 1
77 | F(2) = 1
78 | F(3) = F(2) + F(1) = 2
79 | F(4) = F(3) + F(2) = 3
80 | F(5) = F(4) + F(3) = 5
81 |

82 |

DP Solution defining state and transition

83 |
 84 | int dp[n + 1]; // state
 85 | dp[1] = 1;
 86 | dp[2] = 1;
 87 | for (int i = 3; i <= n; i++)
 88 | {
 89 |     dp[i] = dp[i - 1] + dp[i - 2]; // transition
 90 | }
 91 | cout << dp[n] << endl; // Final subproblem
 92 | 
93 | 94 |

Comparing with and without DP solutions

95 | 96 |

Without DP

97 |
 98 | int functionEntered = 0;
 99 | int helper(int n)
100 | {
101 |     functionEntered++;
102 |     if (n == 1 || n == 2)
103 |         return 1;
104 |     return helper(n - 1) + helper(n - 2);
105 | }
106 | void solve()
107 | {
108 |     int n;
109 |     cin >> n;
110 |     helper(n);
111 |     cout << functionEntered << endl;
112 | }
113 | 
114 |

115 | Here if n = 30 then
116 | functionEntered = 1664079
117 |

118 |

With DP

119 |
120 | int functionEntered = 0;
121 | int dp[40];
122 | int helper(int n)
123 | {
124 |     functionEntered++;
125 |     if (n == 1 || n == 2)
126 |         return 1;
127 |     if (dp[n] != -1)
128 |         return dp[n];
129 |     return dp[n] = helper(n - 1) + helper(n - 2);
130 | }
131 | void solve()
132 | {
133 |     int n;
134 |     cin >> n;
135 |     for (int i = 0; i <= n; i++)
136 |     {
137 |         dp[i] = -1;
138 |     }
139 |     helper(n);
140 |     cout << functionEntered << endl;
141 | }
142 | 
143 | 144 |

145 | Here, if n = 30 then
146 | functionEntered = 57 147 |

148 | 149 | --------------------------------------------------------------------------------