Dynamic Programming (DP)
Solve optimization problems efficiently with the power of overlapping subproblems and optimal substructure.
What is Dynamic Programming?
Dynamic Programming (DP) is a powerful algorithmic paradigm used to solve problems by breaking them into smaller overlapping subproblems. Instead of solving the same subproblem multiple times, DP stores the results in a table (memoization or tabulation) and reuses them to optimize the overall computation.
Key Concepts in DP
- Overlapping Subproblems: The problem can be divided into subproblems that are reused multiple times.
- Optimal Substructure: The solution to the main problem can be built from the solutions to its subproblems.
DP Approaches
-
Top-Down (Memoization):
- Solve the problem recursively.
- Store the results of subproblems in a table to avoid redundant calculations.
-
Bottom-Up (Tabulation):
- Solve the problem iteratively.
- Build a solution by solving all subproblems from the smallest to the largest.
Time & Space Complexities
- Time Complexity:
O(n)toO(n*m)(depends on the problem and state space). - Space Complexity: Can range from
O(n)(space-optimized DP) toO(n*m)(when storing a table of solutions).
When to Use DP
Dynamic Programming is a great choice for problems like:
- Fibonacci numbers.
- Longest Common Subsequence (LCS).
- Knapsack problem.
- Shortest path in a graph (e.g., Floyd-Warshall, Bellman-Ford).
- Partitioning problems (e.g., Palindrome Partitioning, Subset Sum).
Code Examples
Below is an example of solving the Fibonacci sequence problem using both approaches.
#include <iostream>
#include <vector>
using namespace std;
// Bottom-Up Approach (Tabulation)
int fibonacci(int n) {
if (n <= 1) return 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];
}
return dp[n];
}
int main() {
int n = 10;
cout << "Fibonacci(" << n << ") = " << fibonacci(n) << endl;
return 0;
}