10.2 Greedy Algorithms
Understand what a Greedy Algorithm is, when making the best local choice guarantees the global best answer, and when you must use DP instead.
1. What Is a Greedy Algorithm?
A Greedy Algorithm solves an optimization problem step-by-step by always picking the choice that looks best right now (the locally optimal choice) and never undoing or reconsidering past choices.
2. Why Do We Need Greedy & When Does It Work vs. Fail?
Why Greedy Is Amazing When It Works
Backtracking explores an exponential tree (O(2^N)), and Dynamic Programming builds a table of subproblems (O(N^2)). When a problem has the Greedy Choice Property (meaning taking the best immediate step never locks you out of the best overall outcome), Greedy solves it in one O(N) pass with O(1) memory!
How to Tell Greedy vs. Dynamic Programming Apart
- Where Greedy Works (
Jump Game): You stand at index0and eachnums[i]tells you the maximum jump length fromi. Why explore every jump combination? If you can reach indexfarthest, you can also reach every single index beforefarthest! So tracking a single variablefarthest = max(farthest, i + nums[i])is 100% sufficient. - Where Greedy Fails (
Coin Changewith[1, 3, 4], target6): Greedy grabs the biggest coin (4), forcing4 + 1 + 1(3 coins), whereas3 + 3uses only 2 coins! Whenever a greedy choice today can hurt you tomorrow, use Dynamic Programming instead.
Quick Decision Table: Should I Use Greedy or Dynamic Programming?
| Signal in the Problem | Greedy (O(N) or O(N log N)) | Dynamic Programming (O(N^2) or O(N * W)) |
|---|---|---|
| Can items be split or ordered monotonically? | Yes (Fractional Knapsack, Activity Selection, Jump Reach) | No (0/1 Discrete Knapsack, Arbitrary Coin Denominations) |
| Does a local best choice ever block a better future combo? | Never (further jump reach always covers shorter reach) | Yes (picking a large coin or house today blocks adjacent/better sums) |
Warm-Up Example: Assign Cookies (greed = [1,2,3], cookies = [1,1]) | Sort both arrays and give the smallest cookie that satisfies the least greedy child! | Not needed — giving a bigger cookie than necessary never helps another child more. |
Interactive Step-by-Step Trace: Greedy Farthest Reach (Jump Game)
Can we reach the last index in nums = [2, 3, 1, 1, 4]?
As soon as farthest >= 4, we know the end is reachable in O(N) time and O(1) space!
3. Basic Operations & Walkthroughs (All 4 Languages)
# 1. Jump Game: Track the furthest index reachable so far -> O(N) time, O(1) space
def can_jump(nums: list[int]) -> bool:
farthest = 0
for i, jump in enumerate(nums):
if i > farthest:
return False # We landed on an unreachable gap!
farthest = max(farthest, i + jump)
return True
# 2. Gas Station: Reset starting station whenever tank drops below 0 -> O(N)
def can_complete_circuit(gas: list[int], cost: list[int]) -> int:
if sum(gas) < sum(cost):
return -1 # Total fuel < total cost -> impossible!
tank = start = 0
for i in range(len(gas)):
tank += gas[i] - cost[i]
if tank < 0:
start = i + 1
tank = 0
return start4. Core Interview Patterns & Common Traps
Use when finding the MINIMUM number of jumps to reach the end of an array in O(N) time and O(1) space.
jumps = 0
cur_end = farthest = 0
for i in range(len(nums) - 1):
farthest = max(farthest, i + nums[i])
if i == cur_end: # Finished scanning current jump's reach!
jumps += 1
cur_end = farthest # Commit to the furthest reach for next jump
return jumps| Operation / Pattern | Time | Space | Notes |
|---|---|---|---|
| Greedy Reachability / Surplus Scan | O(N) | O(1) | Single pass maintaining running boundary or surplus |