Dynamic Programming (DP) is a powerful algorithmic technique used to solve optimization problems by breaking them down into simpler subproblems. It is particularly useful when a problem has overlapping subproblems and optimal substructure, which means the solution to the problem can be constructed from the solutions to smaller subproblems. DP avoids redundant computations by storing the results of subproblems, which can be reused in solving larger problems. This makes DP more efficient than naive recursive approaches.
Optimal Substructure: The problem can be solved by combining solutions to its subproblems. If the solution to a problem involves solving smaller instances of the same problem, it has optimal substructure.
Overlapping Subproblems: The problem can be broken down into smaller subproblems that are solved multiple times. Dynamic programming exploits this property by storing the results of subproblems in a table (memoization or tabulation), thus eliminating the need to recompute them.
Memoization vs Tabulation:
Define the subproblem: Identify the smaller subproblems that can be solved independently and combined to solve the larger problem.
Recurrence relation: Develop a recurrence relation that expresses the solution to a problem in terms of solutions to smaller subproblems.
Base cases: Identify the simplest subproblems (usually with known solutions) that will serve as the foundation for solving larger subproblems.
Compute solutions: Using memoization or tabulation, compute the solutions to the subproblems, building up to the solution of the original problem.
Solution extraction: Once the subproblems are solved, the result for the original problem is typically stored in the last cell of the DP table or can be directly extracted from the cache.
Input: n = 6
Output: 8 (Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8)
int fib(int n, vector<int>& dp) {
if (n <= 1) return n;
if (dp[n] != -1) return dp[n]; // Return cached result
return dp[n] = fib(n - 1, dp) + fib(n - 2, dp);
}
int fib(int n) {
vector<int> dp(n + 1, 0);
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
int knapsack(int W, vector<int>& weights, vector<int>& values, int n) {
vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= W; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[n][W];
}
int lcs(string s1, string s2) {
int m = s1.length(), n = s2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
int matrixChainOrder(vector<int>& p, int n) {
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int len = 2; len < n; len++) {
for (int i = 1; i < n - len + 1; i++) {
int j = i + len - 1;
dp[i][j] = INT_MAX;
for (int k = i; k < j; k++) {
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + p[i - 1] * p[k] * p[j]);
}
}
}
return dp[1][n - 1];
}
Open this section to load past papers