Which method will save you hours of work and find the best answer fast?
DP vs Recursion vs Greedy Choosing the Right Tool in DSA Go - Why the Distinction Matters
Imagine you want to find the best way to climb stairs where each step can have different rewards. You try every possible path by hand, writing down each choice and its result.
Trying every path manually is slow and confusing. You might miss some paths or repeat the same work many times, making it hard to find the best answer quickly.
Using recursion, dynamic programming (DP), or greedy methods helps you solve the problem smartly. Recursion breaks the problem into smaller parts, DP remembers past results to avoid repeats, and greedy picks the best choice step-by-step.
func climbStairs(n int) int {
if n <= 2 {
return n
}
return climbStairs(n-1) + climbStairs(n-2)
}func climbStairsDP(n int) int {
dp := make([]int, n+1)
dp[1], dp[2] = 1, 2
for i := 3; i <= n; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
return dp[n]
}It lets you solve complex problems efficiently by choosing the right approach, saving time and effort.
Planning the fastest route for delivery trucks where some roads are faster but longer, and others are shorter but slower.
Manual trial is slow and error-prone.
Recursion breaks problems down but can repeat work.
DP remembers past results to speed up solutions.
Greedy picks the best immediate choice but may not always be perfect.
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 parts.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
- Confusing brute force with DP
Solution
Step 1: Check function return type and base case
Factorial returns int, so function must return int. Base case for factorial is n == 0 returning 1.Step 2: Verify recursive call and return statement
Recursive call multiplies n by factorial(n-1). func factorial(n int) int { if n == 0 { return 1 } return n * factorial(n-1) } matches correct signature and logic.Final Answer:
func factorial(n int) int { if n == 0 { return 1 } return n * factorial(n-1) } -> Option BQuick Check:
Correct return type and base case = func factorial(n int) int { if n == 0 { return 1 } return n * factorial(n-1) } [OK]
- Missing return type in function signature
- Incorrect base case value
- Returning wrong type or no return
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
func main() {
fmt.Println(fib(5))
}Solution
Step 1: Trace fib(5) calls
fib(5) = fib(4) + fib(3); fib(4) = fib(3) + fib(2); fib(3) = fib(2) + fib(1); fib(2) = fib(1) + fib(0)Step 2: Calculate values bottom-up
fib(0)=0, fib(1)=1; fib(2)=1; fib(3)=2; fib(4)=3; fib(5)=5Final Answer:
5 -> Option CQuick Check:
Fibonacci(5) = 5 [OK]
- Confusing index starting point
- Miscomputing recursive calls
- Assuming fib(1) = 0
func coinChange(coins []int, amount int) int {
count := 0
for i := len(coins) - 1; i >= 0; i-- {
for amount >= coins[i] {
amount -= coins[i]
count++
}
}
if amount != 0 {
return -1
}
return count
}Solution
Step 1: Understand greedy coin change logic
The code tries largest coins first, subtracting from amount until no longer possible.Step 2: Identify limitation of greedy approach
Greedy works only if coin denominations are canonical (like US coins). Otherwise, it may not find minimum coins or any solution.Final Answer:
Greedy may fail if coins are not canonical denominations -> Option AQuick Check:
Greedy correctness depends on coin set [OK]
- Assuming greedy always finds minimum coins
- Ignoring coin order importance
- Misunderstanding return values
Solution
Step 1: Analyze LIS problem nature
LIS requires checking subsequences and their lengths. Simple recursion tries all but is exponential time.Step 2: Compare approaches
Greedy fails because local choices don't guarantee global LIS. DP stores LIS lengths for subproblems, improving efficiency.Final Answer:
Dynamic Programming is best for LIS -> Option DQuick Check:
LIS problem = DP best approach [OK]
- Choosing greedy for LIS
- Ignoring exponential time of recursion
- Thinking brute force is efficient
Ready to prove it?
Pass the test and this pattern moves unknown
