Can you tell when rushing is good and when careful planning wins?
Greedy vs DP How to Know Which to Apply in DSA Python - Why the Distinction Matters
Imagine you have a big jar of mixed coins and you want to give exact change quickly. You try picking the biggest coins first, hoping it adds up fast. But sometimes, this way doesn't work and you get stuck or give too many coins.
Choosing coins just by picking the biggest first is fast but can fail to find the best answer. It's like rushing without checking all options. This can lead to wrong or costly results, especially when the problem needs careful planning.
Greedy methods pick the best choice at each step, hoping it leads to the best overall. Dynamic Programming (DP) looks at all choices and remembers past results to build the best solution step-by-step. Knowing when to use each saves time and finds the right answer.
coins = [25, 10, 5, 1] amount = 30 result = [] for coin in coins: while amount >= coin: amount -= coin result.append(coin) print(result)
def dp_coin_change(coins, amount): dp = [float('inf')] * (amount + 1) dp[0] = 0 for i in range(1, amount + 1): for coin in coins: if coin <= i: dp[i] = min(dp[i], dp[i - coin] + 1) return dp[amount] print(dp_coin_change([25,10,5,1], 30))
Understanding when to use Greedy or DP lets you solve problems faster and correctly, avoiding costly mistakes.
Choosing the fastest route home: Greedy picks the quickest road now, but DP plans the whole trip considering traffic and stops to find the best overall path.
Greedy picks best immediate choice; DP plans with memory of past steps.
Use Greedy when local choices lead to global best.
Use DP when problem needs exploring many options and remembering results.
Practice
Solution
Step 1: Understand greedy algorithm conditions
Greedy algorithms work when choosing the best option at each step leads to a global optimum, which means the problem has optimal substructure and no overlapping subproblems.Step 2: Compare with dynamic programming conditions
Dynamic programming is used when problems have overlapping subproblems and require storing intermediate results (memoization) to avoid repeated work.Final Answer:
When the problem has optimal substructure and no overlapping subproblems -> Option AQuick Check:
Greedy = optimal substructure + no overlap [OK]
- Confusing overlapping subproblems as a greedy condition
- Thinking greedy always finds the best solution
- Using dynamic programming when greedy suffices
Solution
Step 1: Identify the correct data structure for memoization
Memoization requires storing key-value pairs of subproblem results, so a dictionary (hash map) is appropriate.Step 2: Check the options
memo = {} initializes an empty dictionary, which is correct. Lists or sets do not store key-value pairs, and an integer cannot store multiple results.Final Answer:
memo = {} -> Option BQuick Check:
Memoization uses dictionary = {} [OK]
- Using list or set instead of dictionary for memo
- Initializing memo as a number
- Confusing data structures for memoization
intervals = [(1, 3), (2, 5), (4, 7)]
intervals.sort(key=lambda x: x[1])
selected = []
end = 0
for start, finish in intervals:
if start >= end:
selected.append((start, finish))
end = finish
print(selected)Solution
Step 1: Sort intervals by finish time
Sorted intervals: [(1, 3), (2, 5), (4, 7)]Step 2: Select intervals greedily
Start with end=0. (1,3) start=1 >=0, select it, end=3. Next (2,5) start=2 <3 skip. Next (4,7) start=4 >=3 select it.Final Answer:
[(1, 3), (4, 7)] -> Option CQuick Check:
Greedy interval selection = [(1, 3), (4, 7)] [OK]
- Not sorting intervals by finish time
- Selecting overlapping intervals
- Appending intervals without condition
def fib(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fib(n-1) + fib(n-2)
return memo[n]
print(fib(5))Solution
Step 1: Identify the use of mutable default argument
The function uses memo={} as a default argument, which is mutable and shared across calls, causing unexpected behavior if fib is called multiple times.Step 2: Explain why this is a bug
This can cause memo to retain values from previous calls, leading to incorrect results or memory issues.Final Answer:
Mutable default argument causes incorrect memoization across calls -> Option AQuick Check:
Mutable default dict in function = bug [OK]
- Ignoring mutable default argument issue
- Thinking base cases are missing
- Assuming memo is not updated
Solution
Step 1: Understand greedy approach limitation
Greedy picks largest coin first, but for amount 6, greedy picks 4+1+1=3 coins, but optimal is 3+3=2 coins.Step 2: Recognize dynamic programming advantage
Dynamic programming tries all combinations and finds minimum coins, handling cases where greedy fails.Final Answer:
Dynamic programming is needed because greedy fails for some amounts -> Option DQuick Check:
Coin change needs DP, greedy can fail [OK]
- Assuming greedy always finds minimum coins
- Ignoring counterexamples where greedy fails
- Thinking coin change is always greedy
Ready to prove it?
Pass the test and this pattern moves unknown
