DP vs Recursion vs Greedy Choosing the Right Tool in DSA Python - Complexity Comparison
Choosing between dynamic programming, recursion, and greedy methods affects how fast a solution runs.
We want to know how the time needed grows as the problem size gets bigger.
Analyze the time complexity of these three approaches solving the same problem.
# Recursive Fibonacci (simple recursion)
def fib_recursive(n):
if n <= 1:
return n
return fib_recursive(n-1) + fib_recursive(n-2)
# Dynamic Programming Fibonacci (memoization)
def fib_dp(n, memo=None):
if memo is None:
memo = {}
if n in memo:
return memo[n]
if n <= 1:
memo[n] = n
else:
memo[n] = fib_dp(n-1, memo) + fib_dp(n-2, memo)
return memo[n]
# Greedy approach (not suitable here, but example)
def fib_greedy(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
These functions calculate Fibonacci numbers using recursion, dynamic programming, and a greedy-like iterative method.
Look at what repeats in each method.
- Recursive: Calls itself twice for each number, many repeated calculations.
- Dynamic Programming: Calls itself once per number, stores results to avoid repeats.
- Greedy (Iterative): Loops once from 0 to n, updating values step-by-step.
- Dominant operation: Number of function calls or loop iterations depending on method.
As n grows, the work changes differently for each method.
| Input Size (n) | Recursive Calls | DP Calls | Greedy Iterations |
|---|---|---|---|
| 10 | 177 | 19 | 10 |
| 100 | O(2^100) (huge) | 199 | 100 |
| 1000 | O(2^1000) (impossible) | 1999 | 1000 |
Recursive grows very fast (doubling calls), DP and greedy grow linearly with n.
Time Complexity: O(2^n) for recursion, O(n) for DP and greedy
Recursive method takes exponentially more time, while DP and greedy scale linearly as input grows.
[X] Wrong: "Recursion is always slow and should be avoided."
[OK] Correct: Recursion can be efficient if combined with memoization (DP), reducing repeated work.
Understanding these differences helps you pick the right approach and explain your choice clearly in interviews.
"What if we removed memoization from the DP approach? How would the time complexity change?"
Practice
Solution
Step 1: Understand problem properties
Overlapping subproblems mean the same smaller problems repeat. Optimal substructure means the best solution can be built from best solutions of smaller parts.Step 2: Match approach to properties
Dynamic Programming stores results of subproblems to avoid repeated work, ideal for these properties.Final Answer:
Dynamic Programming -> Option CQuick Check:
Overlapping subproblems + optimal substructure = Dynamic Programming [OK]
- Choosing greedy when problem needs global optimization
- Using simple recursion causing repeated calculations
- Confusing brute force with efficient methods
Solution
Step 1: Check base case and recursion
def fib(n): if n <= 1: return n else: return fib(n-1) + fib(n-2) correctly returns n if n <= 1, else calls fib(n-1) + fib(n-2).Step 2: Verify other options
Options B, C, and D have syntax or logic errors: B uses for loop wrongly, C uses while without recursion, D causes infinite recursion.Final Answer:
def fib(n): if n <= 1: return n else: return fib(n-1) + fib(n-2) -> Option AQuick Check:
Correct recursive base and calls = def fib(n): if n <= 1: return n else: return fib(n-1) + fib(n-2) [OK]
- Missing base case causing infinite recursion
- Using loops instead of recursion incorrectly
- Wrong return statements inside recursion
def max_sum(arr, n):
if n == 0:
return 0
elif n == 1:
return arr[0]
else:
return max(max_sum(arr, n-1), max_sum(arr, n-2) + arr[n-1])
print(max_sum([3, 2, 7, 10], 4))Solution
Step 1: Understand function logic
max_sum returns max sum of non-adjacent elements from first n elements.Step 2: Trace calls for n=4
max_sum(arr,4) = max(max_sum(arr,3), max_sum(arr,2) + arr[3]) max_sum(arr,3) = max(max_sum(arr,2), max_sum(arr,1) + arr[2]) max_sum(arr,2) = max(max_sum(arr,1), max_sum(arr,0) + arr[1]) Calculate bottom-up: max_sum(arr,0)=0 max_sum(arr,1)=3 max_sum(arr,2)=max(3,0+2)=3 max_sum(arr,3)=max(3,3+7)=10 max_sum(arr,4)=max(10,3+10)=13Final Answer:
13 -> Option AQuick Check:
Max sum non-adjacent = 13 [OK]
- Adding adjacent elements mistakenly
- Confusing indices in recursion
- Ignoring base cases
def coin_change(coins, amount):
if amount == 0:
return 1
if amount < 0:
return 0
count = 0
for coin in coins:
count += coin_change(coins, amount - coin)
return count
print(coin_change([1, 2], 3))Solution
Step 1: Analyze recursion logic
The function counts ways to make amount by subtracting coins recursively.Step 2: Identify counting duplicates
Because it tries all coins at each step without order control, it counts same combinations in different orders multiple times.Final Answer:
It counts duplicate combinations multiple times -> Option BQuick Check:
Duplicates counted due to unordered recursion [OK]
- Assuming base cases are missing
- Confusing negative amount handling
- Thinking iteration replaces recursion
Solution
Step 1: Understand problem requirements
We want minimum coins to make amount, which requires checking all combinations to find optimal.Step 2: Evaluate approaches
Greedy fails here because picking largest coin first may not yield minimum coins (e.g., amount=6, greedy picks 4+1+1=3 coins, but 3+3=2 coins is better). Simple recursion repeats subproblems causing inefficiency. DP stores results to avoid repeated work and finds optimal solution efficiently.Final Answer:
Dynamic Programming to store and reuse subproblem results -> Option DQuick Check:
Minimum coins with overlapping subproblems = DP [OK]
- Assuming greedy always works for coin change
- Ignoring efficiency of memoization
- Using brute force causing timeouts
Ready to prove it?
Pass the test and this pattern moves unknown
