DP vs Recursion vs Greedy Choosing the Right Tool in DSA Javascript - Complexity Comparison
Choosing between dynamic programming, recursion, and greedy methods affects how fast your code 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)
function fibRec(n) {
if (n <= 1) return n;
return fibRec(n - 1) + fibRec(n - 2);
}
// Dynamic Programming Fibonacci (memoization)
function fibDP(n, memo = {}) {
if (n <= 1) return n;
if (memo[n] !== undefined) return memo[n];
memo[n] = fibDP(n - 1, memo) + fibDP(n - 2, memo);
return memo[n];
}
// Greedy example (coin change with unlimited coins)
function coinChange(coins, amount) {
coins.sort((a, b) => b - a);
let count = 0;
for (let coin of coins) {
while (amount >= coin) {
amount -= coin;
count++;
}
}
return amount === 0 ? count : -1;
}
These snippets show recursion, dynamic programming, and greedy methods solving problems differently.
Look at loops and repeated calls that cost time.
- Recursive Fibonacci: Calls itself twice per step, repeating many calculations.
- DP Fibonacci: Calls itself once per unique input, stores results to avoid repeats.
- Greedy coin change: Loops over coins and subtracts repeatedly until done.
- Dominant operation: Recursive calls in recursion, loop iterations in greedy.
See how the number of steps grows as input size increases.
| Input Size (n) | Recursive Fib Calls | DP Fib Calls | Greedy Coin Steps |
|---|---|---|---|
| 10 | ~177 calls | 10 calls | Depends on coins, e.g. 5 steps |
| 20 | ~21,891 calls | 20 calls | More steps but linear to amount |
| 30 | ~2,692,537 calls | 30 calls | Still linear to amount |
Recursive calls grow very fast, DP grows slowly, greedy depends on amount but is usually fast.
Time Complexity: O(2^n) recursion, O(n) DP, O(m * k) greedy
Recursion can be very slow, DP speeds it up by remembering answers, greedy is fast but only works when choices are simple.
[X] Wrong: "Greedy always gives the best answer quickly."
[OK] Correct: Greedy works only when local best choices lead to global best; otherwise, it can fail or give wrong answers.
Understanding these methods helps you pick the right tool for problems, showing clear thinking and efficiency in your solutions.
"What if we added memoization to the greedy coin change? How would the time complexity change?"
Practice
Solution
Step 1: Understand overlapping subproblems and optimal substructure
Dynamic Programming is designed for problems where subproblems repeat and solutions build on smaller solutions.Step 2: Compare with other approaches
Greedy algorithms do not always consider overlapping subproblems, recursion without memoization repeats work, and brute force is inefficient.Final Answer:
Dynamic Programming -> Option AQuick Check:
Overlapping subproblems + optimal substructure = Dynamic Programming [OK]
- Confusing greedy with DP for overlapping subproblems
- Ignoring memoization in recursion
- Choosing brute force for efficiency
Solution
Step 1: Check base case correctness
function factorial(n) { if (n === 0) return 1; return n * factorial(n - 1); } returns 1 when n is 0, which is correct for factorial.Step 2: Check recursive call and return statement
function factorial(n) { if (n === 0) return 1; return n * factorial(n - 1); } correctly multiplies n by factorial(n-1). Others have incorrect loops or wrong base cases.Final Answer:
function factorial(n) { if (n === 0) return 1; return n * factorial(n - 1); } -> Option BQuick Check:
Correct base case and recursive call = function factorial(n) { if (n === 0) return 1; return n * factorial(n - 1); } [OK]
- Using loops inside recursion incorrectly
- Wrong base case value
- Returning addition instead of multiplication for factorial
function fib(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}
console.log(fib(5));Solution
Step 1: Understand the Fibonacci function with memoization
fib(5) calculates the 5th Fibonacci number using memo to avoid repeated work.Step 2: Calculate fib(5) step-by-step
fib(0)=0, fib(1)=1, fib(2)=1, fib(3)=2, fib(4)=3, fib(5)=5.Final Answer:
5 -> Option CQuick Check:
fib(5) = 5 with memoization [OK]
- Confusing Fibonacci index with value
- Ignoring memo object causing slow recursion
- Mixing up base cases
function coinChange(coins, amount) {
coins.sort();
let count = 0;
for (let i = coins.length - 1; i >= 0; i--) {
while (amount >= coins[i]) {
amount -= coins[i];
count++;
}
}
return amount === 0 ? count : -1;
}Solution
Step 1: Check sorting order for greedy coin change
Greedy coin change requires coins sorted descending to pick largest first.Step 2: Identify sorting mistake
coins.sort() sorts ascending by default, so it should be coins.sort((a,b) => b - a).Final Answer:
coins.sort() sorts ascending, should sort descending -> Option DQuick Check:
Sort descending for greedy coin change [OK]
- Using default ascending sort
- Ignoring sorting affects greedy choice
- Assuming while loop is wrong here
const arr = [2,3,1,1,4];
Solution
Step 1: Understand the problem nature
Minimum jumps problem can be solved efficiently by greedy approach choosing the farthest reachable index.Step 2: Compare approaches
Recursion without memoization and brute force are inefficient; DP is possible but greedy is optimal and simpler here.Final Answer:
Greedy, because it chooses the farthest reachable index at each step -> Option AQuick Check:
Minimum jumps = Greedy approach [OK]
- Using recursion without memoization causing timeout
- Overcomplicating with DP when greedy suffices
- Trying brute force leading to inefficiency
Ready to prove it?
Pass the test and this pattern moves unknown
