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 your code runs.
We want to see how the number of steps changes with input size for each approach.
Analyze the time complexity of these three approaches solving the same problem.
// Recursive Fibonacci (simple recursion)
int fibRec(int n) {
if (n <= 1) return n;
return fibRec(n-1) + fibRec(n-2);
}
// Dynamic Programming (memoization)
int fibDP(int n, int memo[]) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = fibDP(n-1, memo) + fibDP(n-2, memo);
return memo[n];
}
// Greedy approach (not suitable here, but example)
// For problems where local choice leads to global optimum
// Example: coin change with unlimited coins and greedy coin set
int greedyCoinChange(int amount, int coins[], int size) {
int count = 0;
for (int i = size - 1; i >= 0; i--) {
while (amount >= coins[i]) {
amount -= coins[i];
count++;
}
}
return count;
}
This code shows recursive, dynamic programming, and greedy methods for different problems.
Look at the loops and calls that repeat work.
- Recursive Fibonacci: Calls itself twice per call, many repeated calculations.
- Dynamic Programming: Each subproblem solved once, stored in memo array.
- Greedy Coin Change: Loops over coins and subtracts from amount repeatedly.
- Dominant operation: Recursive calls for recursion, single pass for DP and greedy.
See how steps increase as input size grows.
| Input Size (n) | Recursive Calls / Steps | DP Calls / Steps | Greedy Steps |
|---|---|---|---|
| 10 | ~177 calls | 10 calls | Depends on amount and coins |
| 20 | ~21,891 calls | 20 calls | Depends on amount and coins |
| 30 | ~2,692,537 calls | 30 calls | Depends on amount and coins |
Recursive calls grow very fast (exponentially), DP grows linearly, greedy depends on input but usually linear.
Time Complexity: O(2^n) for recursion, O(n) for DP, O(m) for greedy
Recursion repeats work many times, DP avoids repeats, greedy quickly picks choices.
[X] Wrong: "Recursion is always slow and should be avoided."
[OK] Correct: Recursion can be efficient with memoization (DP) and is sometimes the simplest way to express a solution.
Understanding these differences helps you pick the right tool and explain your choices clearly in interviews.
"What if we changed the recursive Fibonacci to use bottom-up iteration instead of memoization? How would the time complexity change?"
Practice
Solution
Step 1: Understand problem characteristics
Overlapping subproblems means 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 recomputation, 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 memo causing slow code
- Confusing optimal substructure with greedy choice property
Solution
Step 1: Understand memoization concept
Memoization stores results of recursive calls to avoid repeated work.Step 2: Identify correct syntax usage
Using a global or passed array/vector to save results and checking it before recursion is the correct way.Final Answer:
Use a global array or vector to store results and check before recursion -> Option DQuick Check:
Memoization = store results before recursion [OK]
- Not storing results causing repeated calculations
- Using loops inside recursion incorrectly
- Calling recursion multiple times without memo
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
int main() {
std::cout << fib(4);
return 0;
}Solution
Step 1: Trace fib(4) calls
fib(4) = fib(3) + fib(2) - fib(3) = fib(2) + fib(1) = (fib(1)+fib(0)) + 1 = (1+0)+1=2 - fib(2) = fib(1) + fib(0) = 1 + 0 = 1 So fib(4) = 2 + 1 = 3Step 2: Confirm output
The program prints 3 as the result of fib(4).Final Answer:
3 -> Option CQuick Check:
fib(4) = 3 [OK]
- Confusing fib(4) with fib(5)
- Adding wrong Fibonacci numbers
- Assuming fib(4) equals 4
int coinChange(int amount, vector<int>& coins) {
int count = 0;
for (int i = 0; i < coins.size(); i++) {
while (amount >= coins[i]) {
amount -= coins[i];
count++;
}
}
return count;
}Solution
Step 1: Analyze greedy coin change logic
Greedy coin change requires coins sorted descending to pick largest coin first.Step 2: Identify error in code
The code loops coins in given order; if coins are not sorted descending, greedy fails to minimize count.Final Answer:
Coins vector is not sorted in descending order -> Option AQuick Check:
Greedy coin change needs descending sorted coins [OK]
- Not sorting coins descending
- Returning wrong variable
- Thinking while loop inside for is wrong
Solution
Step 1: Understand problem requirements
We want minimum coins for amount using coins [1,3,4]. Greedy fails here because picking largest coin first may not give minimum coins.Step 2: Choose best approach
Dynamic Programming stores minimum coins for all smaller amounts, building up to the target efficiently and correctly.Final Answer:
Dynamic Programming to store minimum coins needed for sub-amounts -> Option BQuick Check:
Minimum coins with tricky denominations = DP [OK]
- Assuming greedy always works for coin change
- Using recursion without memo causing slow code
- Trying brute force which is inefficient
Ready to prove it?
Pass the test and this pattern moves unknown
