Dynamic Programming (DP) is an optimization technique used to solve complex problems by breaking them down into smaller overlapping subproblems. It is typically used when a problem has the following characteristics:
- Overlapping Subproblems: The problem can be broken down into smaller subproblems that are reused multiple times.
- Optimal Substructure: The optimal solution of the problem can be constructed from optimal solutions of its subproblems.
DP problems are usually solved using one of the two approaches:
- Top-Down Approach (Memoization): Solve the problem recursively and store results to avoid redundant computations.
- Bottom-Up Approach (Tabulation): Solve subproblems iteratively and store results in a table to build up to the final solution.
- Fibonacci Sequence (Recursive + Memoization, Iterative)
- Factorial of a Number
- Binomial Coefficient Calculation
- Climbing Stairs (Ways to climb N stairs with 1 or 2 steps at a time)
- House Robber (Maximize sum by robbing non-adjacent houses)
- Maximum Subarray (Kadane's Algorithm) (Find the maximum sum subarray)
- Coin Change (Minimum number of coins to make amount N)
- Longest Common Subsequence (LCS) (Find longest subsequence common to two strings)
- Longest Palindromic Subsequence
- Edit Distance (Levenshtein Distance) (Minimum operations to convert one string to another)
- Knapsack Problem (0/1 Knapsack)
- Unique Paths (Count paths in an MxN grid with right/down movements)
- Minimum Path Sum (Find the minimum cost path in a grid)
- Binary Tree Maximum Path Sum
- Diameter of a Binary Tree
- Tree DP (Finding the largest independent set, etc.)
- Floyd-Warshall Algorithm (All-pairs shortest paths)
- Bellman-Ford Algorithm (Single-source shortest path for graphs with negative weights)
- Matrix Chain Multiplication
- Egg Dropping Puzzle
- Partition DP (Palindrome Partitioning, Burst Balloons, etc.)
from functools import lru_cache
def fibonacci(n):
@lru_cache(None)
def helper(x):
if x <= 1:
return x
return helper(x-1) + helper(x-2)
return helper(n)
print(fibonacci(10)) def fibonacci(n):
if n <= 1:
return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(fibonacci(10)) - Identify Overlapping Subproblems: If a problem can be broken into smaller subproblems that are reused, DP might be applicable.
- Define the State: Determine the variables that represent the problem's state.
- Formulate Recurrence Relation: Express the problem in terms of its subproblems.
- Choose Memoization or Tabulation: Decide whether to solve recursively with caching or iteratively using a table.
- Optimize Space Complexity: If only a few previous states are needed, reduce the DP table size.
This guide provides a structured approach to understanding and solving dynamic programming problems. Happy coding!