Dynamic Programming
Dynamic Programming
1. Overlapping Subproblems
2. Optimal Substructure
o The optimal solution of the problem can be built from the optimal solutions of its
subproblems
o Solve problems iteratively and build the answer from the ground up using a DP table
🌀 Top-Down (Memoization)
python
CopyEdit
if n <= 1:
return n
if n not in memo:
return memo[n]
📊 Bottom-Up (Tabulation)
python
CopyEdit
def fib(n):
if n <= 1:
return n
dp = [0] * (n+1)
dp[1] = 1
return dp[n]
python
CopyEdit
for i in range(n+1):
for w in range(W+1):
if i == 0 or w == 0:
dp[i][w] = 0
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
🧰 Common DP Problems
Problem: Given a staircase with n steps, and you can climb 1 or 2 steps at a time, how many distinct
ways to reach the top?
Top-Down DP means you start solving the problem from the original big problem and recursively
break it down into smaller subproblems. You store the results of subproblems in a cache (memo) to
avoid recomputation.
🧩 How It Works
Write the problem recursively
python
CopyEdit
if n <= 1:
return n
if n not in memo:
return memo[n]
print(fib(10)) # Output: 55
python
CopyEdit
if n == 0 or W == 0:
return 0
if (n, W) in memo:
if wt[n-1] > W:
else:
# Usage
W = 50
n = len(val)
memo = {}
Good for problems where not all subproblems are needed (saves work)
🕒 Complexity
Bottom-up
Bottom-Up DP solves the problem by starting from the smallest subproblems first and builds up
solutions for bigger subproblems iteratively, usually using a table (array or matrix).
🧩 How It Works
Use the recurrence relation to fill the table from smallest to largest subproblems.
The final answer is found at the bottom (or the last cell) of the table.
python
CopyEdit
def fib(n):
if n <= 1:
return n
dp = [0] * (n+1)
dp[1] = 1
return dp[n]
print(fib(10)) # Output: 55
python
CopyEdit
if wt[i-1] <= w:
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
n = len(val)
🕒 Complexity
Time and space complexity often same as top-down, but no recursion stack cost.
Ease of Understanding Intuitive for recursive problems May require more careful planning
Use Case When subproblems may be skipped When all subproblems are needed
Recursion
Recursion is a programming technique where a function calls itself to solve smaller instances of the
same problem until it reaches a base case.
Base Case: The simplest instance of the problem, solved directly without further recursion.
Prevents infinite recursion.
python
CopyEdit
def factorial(n):
if n == 0 or n == 1: # Base case
return 1
else:
python
CopyEdit
def recursive_function(parameters):
if base_condition:
return base_value
else:
return recursive_function(smaller_parameters)
⚠️Key Points
Recursive calls build a call stack; too deep recursion may cause stack overflow.
🕒 Time Complexity
Often exponential without memoization, but can be optimized to polynomial with DP.
Memoization
Memoization is an optimization technique used with recursion to store results of expensive function
calls and reuse them when the same inputs occur again, avoiding redundant computations.
🔍 Why Memoization?
python
CopyEdit
if n <= 1:
return n
if n not in memo:
return memo[n]
print(fib(10)) # Output: 55
4. If no, compute and store the result in the cache before returning it.
📈 Benefits
Try all possible solutions until you find the correct one.
3. Greedy Algorithms
Works only when problem has the greedy-choice property and optimal substructure.
5. Backtracking
Abandon a path as soon as it’s clear it won’t lead to a valid solution (prune the search tree).
Similar to backtracking but uses bounds to prune search space more aggressively.
7. Recursion
8. Bit Manipulation