Solve 10 classic greedy interview problems — gas station, candy distribution, jump game, task scheduler, assign cookies, lemonade change, boats to save people, partition labels, minimum arrows, and queue reconstruction by height — with proofs and pattern recognition.
Problem 1 — Gas Station
Can you complete a full circle? If yes, find the starting station.
def can_complete_circuit(gas, cost):
"""
Greedy insight: If total gas >= total cost, a solution exists.
If we run out at station i, the start must be after i.
Time: O(n), Space: O(1)
"""
total_gas = sum(gas) - sum(cost)
if total_gas < 0:
return -1 # Impossible
start = 0
tank = 0
for i in range(len(gas)):
tank += gas[i] - cost[i]
if tank < 0:
start = i + 1 # Current start doesn't work, try next
tank = 0
return start
print(can_complete_circuit([1,2,3,4,5],[3,4,5,1,2])) # 3
print(can_complete_circuit([2,3,4],[3,4,3])) # -1
Problem 3 — Jump Game (Can You Reach the End?)
def can_jump(nums):
"""
Track max reachable index as we scan.
Greedy: if i > max_reach, we're stuck.
Time: O(n), Space: O(1)
"""
max_reach = 0
for i, jump in enumerate(nums):
if i > max_reach:
return False # Cannot reach index i
max_reach = max(max_reach, i + jump)
return True
print(can_jump([2,3,1,1,4])) # True
print(can_jump([3,2,1,0,4])) # False
Problem 4 — Task Scheduler
Minimum intervals to execute tasks with n cooldown between same tasks.
from collections import Counter
import heapq
def least_interval(tasks, n):
"""
Greedy: always execute the most frequent remaining task.
Use a cooldown queue to respect the constraint.
Time: O(T log T) where T = number of unique tasks.
"""
freq = Counter(tasks)
max_heap = [-f for f in freq.values()]
heapq.heapify(max_heap)
time = 0
cooldown = [] # (available_time, neg_freq)
while max_heap or cooldown:
time += 1
if max_heap:
freq_left = heapq.heappop(max_heap) + 1 # Execute task
if freq_left < 0: # More instances remain
heapq.heappush(cooldown, (time + n, freq_left))
# Release tasks that have cooled down
if cooldown and cooldown[0][0] <= time:
_, f = heapq.heappop(cooldown)
heapq.heappush(max_heap, f)
return time
# Mathematical approach (faster):
def least_interval_math(tasks, n):
freq = sorted(Counter(tasks).values(), reverse=True)
f_max = freq[0]
idle_slots = (f_max - 1) * n
for f in freq[1:]:
idle_slots -= min(f_max - 1, f)
return len(tasks) + max(0, idle_slots)
print(least_interval(["A","A","A","B","B","B"], 2)) # 8
print(least_interval(["A","A","A","B","B","B"], 0)) # 6
Problem 5 — Assign Cookies
def find_content_children(g, s):
"""
Greedily satisfy smallest greed factor children first.
Give each child the smallest cookie that satisfies them.
Time: O(n log n + m log m)
"""
g.sort(); s.sort()
child = cookie = 0
while child < len(g) and cookie < len(s):
if s[cookie] >= g[child]:
child += 1 # Child satisfied
cookie += 1 # Move to next cookie regardless
return child # Number of content children
print(find_content_children([1,2,3],[1,1])) # 1
print(find_content_children([1,2],[1,2,3])) # 2
Problem 6 — Lemonade Change
def lemonade_change(bills):
"""
Can you make change for every customer?
Greedy: when making $15 change, prefer to give $10+$5 (preserves $5s).
Time: O(n), Space: O(1)
"""
fives = tens = 0
for bill in bills:
if bill == 5:
fives += 1
elif bill == 10:
if fives == 0: return False
fives -= 1; tens += 1
else: # bill == 20
if tens > 0 and fives > 0:
tens -= 1; fives -= 1 # Prefer $10+$5 (saves $5 bills)
elif fives >= 3:
fives -= 3
else:
return False
return True
print(lemonade_change([5,5,5,10,20])) # True
print(lemonade_change([5,5,10,10,20])) # False
Problem 7 — Boats to Save People
def num_rescue_boats(people, limit):
"""
Pair heaviest with lightest person if possible.
Two-pointer greedy.
Time: O(n log n)
"""
people.sort()
left, right = 0, len(people) - 1
boats = 0
while left <= right:
if people[left] + people[right] <= limit:
left += 1 # Lightest can ride with heaviest
right -= 1 # Heaviest always takes one boat
boats += 1
return boats
print(num_rescue_boats([1,2],[3])) # 1
print(num_rescue_boats([3,2,2,1],[3])) # 3
print(num_rescue_boats([3,5,3,4],[5])) # 4
Problem 8 — Partition Labels
def partition_labels(s):
"""
Partition string so each character appears in at most one part.
Greedy: for each position, extend partition to last occurrence of all chars seen.
"""
last = {c: i for i, c in enumerate(s)} # Last index of each char
partitions = []
start = end = 0
for i, c in enumerate(s):
end = max(end, last[c]) # Extend to cover last occurrence of c
if i == end: # Reached end of current partition
partitions.append(end - start + 1)
start = i + 1
return partitions
print(partition_labels("ababcbacadefegdehijhklij")) # [9,7,8]
Problem 9 — Minimum Arrows to Burst Balloons
def find_min_arrow_shots(points):
"""
Minimum arrows to burst all balloons (intervals).
Sort by end, shoot at end of each non-covered balloon.
Same pattern as activity selection!
Time: O(n log n)
"""
points.sort(key=lambda x: x[1])
arrows = 1
arrow_pos = points[0][1] # Shoot at end of first balloon
for start, end in points[1:]:
if start > arrow_pos: # This balloon not hit by current arrow
arrows += 1
arrow_pos = end
return arrows
print(find_min_arrow_shots([[10,16],[2,8],[1,6],[7,12]])) # 2
print(find_min_arrow_shots([[1,2],[3,4],[5,6],[7,8]])) # 4
Problem 10 — Queue Reconstruction by Height
def reconstruct_queue(people):
"""
people[i] = [h, k] where h=height, k=number of taller/equal people in front.
Greedy: sort by height desc (ties by k asc), then insert at position k.
"""
people.sort(key=lambda x: (-x[0], x[1]))
result = []
for person in people:
result.insert(person[1], person) # Insert at position k
return result
print(reconstruct_queue([[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]))
# [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
Pattern Recognition
| Trigger in Problem | Likely Greedy Strategy |
|---|
| "Minimum cost/steps" | Sort by some criterion, process greedily |
| "Maximum compatible items" | Earliest end time (interval scheduling) |
| "Fill remaining with best" | Value density ratio (knapsack) |
| "Always feasible?" | Check total balance (gas station) |
| "Pair items" | Sort, use two pointers from both ends |
| "Partition into sections" | Track maximum reachable endpoint |
| "Satisfy constraints" | Smallest-first or largest-first pairing |
*Greedy Algorithms section complete.*