Which tool will save you time and headaches when solving tricky problems?
DP vs Recursion vs Greedy Choosing the Right Tool in DSA Typescript - Why the Distinction Matters
Imagine you want to solve a puzzle where you must find the best way to collect coins along a path. You try every possible way by hand, writing down each step and checking if it leads to more coins.
Trying every possible way by hand is slow and confusing. You might forget some paths or repeat the same steps many times, making it easy to make mistakes and waste time.
Using smart methods like recursion, greedy, or dynamic programming helps you solve the puzzle faster and without errors. Each method chooses the best steps in a clever way, saving time and effort.
function collectCoins(path: number[]): number {
let maxCoins = 0;
// Try all paths manually
for (let i = 0; i < path.length; i++) {
// complex checks here
}
return maxCoins;
}function collectCoinsDP(path: number[]): number {
if (path.length === 0) return 0;
const dp: number[] = Array(path.length).fill(0);
dp[0] = path[0];
for (let i = 1; i < path.length; i++) {
const prev2 = i >= 2 ? dp[i - 2] : 0;
dp[i] = Math.max(dp[i - 1], prev2 + path[i]);
}
return dp[path.length - 1];
}These tools let you solve complex problems quickly and correctly by choosing the best approach for each situation.
Planning the fastest route to visit friends in different cities, where you must decide whether to go directly or stop at some places to save time and cost.
Manual checking is slow and error-prone.
Recursion tries all options but can be slow.
Greedy picks the best step now but may miss the best overall.
Dynamic programming remembers past results to be fast and correct.
Practice
Solution
Step 1: Understand overlapping subproblems
Dynamic Programming works well when subproblems repeat and can be reused.Step 2: Check optimal substructure property
Dynamic Programming uses optimal solutions of subproblems to build the final solution.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 brute force with DP
Solution
Step 1: Check base case correctness
function fib(n: number): number { return n <= 1 ? n : fib(n-1) + fib(n-2); } returns n if n <= 1, which correctly handles fib(0)=0 and fib(1)=1.Step 2: Check recursive calls
function fib(n: number): number { return n <= 1 ? n : fib(n-1) + fib(n-2); } calls fib(n-1) + fib(n-2), matching Fibonacci definition.Final Answer:
function fib(n: number): number { return n <= 1 ? n : fib(n-1) + fib(n-2); } -> Option AQuick Check:
Correct base case and recursive sum = function fib(n: number): number { return n <= 1 ? n : fib(n-1) + fib(n-2); } [OK]
- Missing base case causing infinite recursion
- Using multiplication instead of addition
- Incorrect loop instead of recursion
function factorial(n: number): number {
if (n === 0) return 1;
return n * factorial(n - 1);
}
console.log(factorial(4));Solution
Step 1: Trace recursive calls for factorial(4)
factorial(4) = 4 * factorial(3), factorial(3) = 3 * factorial(2), factorial(2) = 2 * factorial(1), factorial(1) = 1 * factorial(0), factorial(0) = 1.Step 2: Calculate values bottom-up
factorial(0) = 1, factorial(1) = 1*1=1, factorial(2) = 2*1=2, factorial(3) = 3*2=6, factorial(4) = 4*6=24.Final Answer:
24 -> Option DQuick Check:
4! = 24 [OK]
- Confusing factorial with sum
- Missing base case causing stack overflow
- Incorrect multiplication order
function minCoins(coins: number[], amount: number): number {
coins.sort((a, b) => b - a);
let count = 0;
for (let coin of coins) {
count += Math.floor(amount / coin);
amount %= coin;
}
return count;
}
console.log(minCoins([1, 3, 4], 6));Solution
Step 1: Understand greedy approach for coin change
Greedy picks largest coin first, but it may not yield minimum coins for all coin sets.Step 2: Check example with coins [1,3,4] and amount 6
Greedy picks 4 once, then 1 twice = 3 coins; but optimal is 3+3 = 2 coins.Final Answer:
Greedy fails for coin sets where local choice isn't optimal -> Option BQuick Check:
Greedy not always optimal for coin change [OK]
- Assuming greedy always works for coin change
- Ignoring coin sorting order
- Confusing floor and ceil division
const arr = [10, 9, 2, 5, 3, 7, 101, 18];Solution
Step 1: Analyze problem characteristics
LIS requires checking many subsequences with overlapping subproblems and optimal substructure.Step 2: Choose approach with efficiency
Dynamic Programming stores LIS lengths for subproblems, avoiding repeated work and improving speed.Final Answer:
Dynamic Programming, to store LIS lengths and build solution efficiently -> Option CQuick Check:
LIS best solved by DP due to overlapping subproblems [OK]
- Using greedy which fails for LIS
- Recursion without memoization causing slow code
- Trying brute force which is inefficient
Ready to prove it?
Pass the test and this pattern moves unknown
