DP vs Recursion vs Greedy Choosing the Right Tool in DSA C - Complexity Comparison
Choosing between dynamic programming, recursion, and greedy methods affects how fast a solution runs.
We want to know how the time needed changes as the problem size grows.
Analyze the time complexity of these three approaches solving the same problem.
// Recursive Fibonacci (simple recursion)
int fib_recursive(int n) {
if (n <= 1) return n;
return fib_recursive(n-1) + fib_recursive(n-2);
}
// Dynamic Programming Fibonacci (bottom-up)
int fib_dp(int n) {
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];
}
// Greedy example (coin change with unlimited coins)
int coin_change_greedy(int coins[], int size, int amount) {
int count = 0;
for (int i = size - 1; i >= 0; i--) {
while (amount >= coins[i]) {
amount -= coins[i];
count++;
}
}
return count;
}
These snippets show recursion, dynamic programming, and greedy approaches for common problems.
Look at loops and repeated calls that affect speed.
- Recursive Fibonacci: Calls itself twice per call, many repeated calculations.
- DP Fibonacci: One loop from 2 to n, calculates each value once.
- Greedy coin change: Outer loop over coin types, inner loop subtracts coins until amount is small.
- Dominant operation: Recursive calls in recursion, single loop in DP, nested loops in greedy.
See how operations increase as input size grows.
| Input Size (n) | Recursive Fib Calls | DP Fib Steps | Greedy Coin Steps |
|---|---|---|---|
| 10 | ~177 calls | 10 steps | Depends on amount and coins |
| 20 | ~21,891 calls | 20 steps | Depends on amount and coins |
| 30 | ~2,692,537 calls | 30 steps | Depends on amount and coins |
Recursive calls grow very fast, DP grows slowly and linearly, greedy depends on coin values and amount.
Time Complexity: O(2^n) for recursion, O(n) for DP, O(k * (amount / min_coin)) for greedy
Recursion can be very slow, DP is efficient by reusing results, greedy depends on problem specifics.
[X] Wrong: "Greedy always gives the best answer quickly."
[OK] Correct: Greedy works only if the problem has a special structure; otherwise, it can give wrong answers.
Understanding these approaches helps you pick the right tool for different problems and explain your choices clearly.
"What if we add memoization to the recursive Fibonacci? How would the time complexity change?"
Practice
Solution
Step 1: Understand problem characteristics
Overlapping subproblems mean the same smaller problems repeat. Optimal substructure means the best solution can be built from best solutions of smaller problems.Step 2: Match approach to characteristics
Dynamic Programming stores results of subproblems to avoid repeated work, ideal for overlapping subproblems and optimal substructure.Final Answer:
Dynamic Programming -> Option AQuick Check:
Overlapping subproblems + optimal substructure = Dynamic Programming [OK]
- Choosing greedy when subproblems overlap
- Using simple recursion without memoization causing slow code
- Confusing optimal substructure with greedy choice property
Solution
Step 1: Understand memoization storage
Memoization requires storing results outside the recursive calls to reuse them. A global or static array is suitable.Step 2: Check options for correct usage
Only a global array checked before recursion ensures stored results are reused properly. Local variables reset each call, static without init may cause errors, pointers need allocation.Final Answer:
Use a global array to store results and check before recursive calls -> Option AQuick Check:
Memoization needs persistent storage = global array [OK]
- Using local variables that reset each call
- Not initializing static variables properly
- Using pointers without memory allocation
int coins[] = {25, 10, 5, 1};
int amount = 30;
int count = 0;
for (int i = 0; i < 4; i++) {
while (amount >= coins[i]) {
amount -= coins[i];
count++;
}
}
printf("%d\n", count);Solution
Step 1: Trace the greedy coin selection
Start with 30 amount. Use 25 coin once (30-25=5), count=1. Then 10 coin can't be used (5<10). Use 5 coin once (5-5=0), count=2.Step 2: Count total coins used
Coins used: 25 (1), 5 (1), total count = 2 coins. The code increments count inside while loop, so count=2.Step 3: Check for remaining coins
Amount is now 0, so no more coins used.Final Answer:
2 -> Option BQuick Check:
Greedy coin count for 30 = 2 coins [OK]
- Counting coins incorrectly by missing loops
- Assuming greedy always gives minimum coins
- Confusing coin values and counts
int factorial(int n) {
if (n == 0) return 0;
return n * factorial(n - 1);
}Solution
Step 1: Analyze base case correctness
Factorial of 0 is 1, but code returns 0, which will cause all results to be zero.Step 2: Suggest correct base case
Change base case to return 1 when n == 0 to correctly end recursion and compute factorial.Final Answer:
Base case should return 1 instead of 0 -> Option CQuick Check:
Factorial(0) = 1 base case [OK]
- Returning 0 in base case causing zero product
- Changing recursion direction incorrectly
- Ignoring base case leading to infinite recursion
Solution
Step 1: Understand problem requirements
Minimum coins for amount with coins {1,3,4} needs checking all combinations to find optimal solution.Step 2: Evaluate approaches
Greedy fails here because picking largest coin first may not yield minimum coins. Recursion without memoization is slow. Dynamic Programming stores results of smaller amounts to build 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
- Using recursion without memo causing slow code
- Ignoring overlapping subproblems in coin change
Ready to prove it?
Pass the test and this pattern moves unknown
