Scope — Taking the locally best choice and the exchange argument that proves it safe — interval scheduling, jump games, task assignment — plus how to spot when greedy fails and DP is required.
See also: greedy_examples.md — the fourteen worked problems behind these templates; intervals.md — the interval-specific greedy family; heap.md — greedy that needs the current best repeatedly; dp.md — what to fall back to when the exchange argument breaks; sort.md — nearly every greedy starts with a sort.
Greedy algorithms make locally optimal choices at each step with the hope of finding a global optimum. They work by selecting the best available option at each decision point without reconsidering previous choices.
- Time Complexity: Usually O(n) or O(nlogn) with sorting
- Space Complexity: O(1) to O(n) depending on problem
- Core Idea: Make the locally optimal choice at each step
- When to Use: Problems with greedy choice property and optimal substructure
- Limitation: Doesn’t always yield globally optimal solution
- Greedy Choice Property: Local optimal leads to global optimal
- Optimal Substructure: Optimal solution contains optimal sub-solutions
- No Backtracking: Once a choice is made, it’s never reconsidered
- Proof Required: Must prove greedy approach gives optimal result
- Greedy vs DP: Greedy is optimized DP when greedy choice works
- Greedy vs Brute Force: Much faster but may miss optimal
- Path: Brute Force → DP → Greedy (when applicable)
| Template Type |
Use Case |
Sorting Key |
When to Use |
| Interval |
Non-overlapping selection |
End time |
Meeting rooms, activities |
| Priority Queue |
Dynamic selection |
Value/frequency |
Task scheduling |
| Two Pointers |
Pairing/matching |
Various |
Array manipulation |
| Accumulation |
Running sum/product |
None |
Stock, gas station |
| Jump/Reach |
Position tracking |
None |
Jump games |
def greedy_solution(items):
items.sort(key=lambda x: x[criterion])
result = initial_value
current_state = initial_state
for item in items:
if can_select(item, current_state):
result = update_result(result, item)
current_state = update_state(current_state, item)
return result
★★★★★Priority 5 of 5 — Must know — expect it in almost every loop
def interval_scheduling(intervals):
"""Select maximum non-overlapping intervals"""
if not intervals:
return 0
intervals.sort(key=lambda x: x[1])
count = 1
end = intervals[0][1]
for i in range(1, len(intervals)):
if intervals[i][0] >= end:
count += 1
end = intervals[i][1]
return count
★★★★☆Priority 4 of 5 — High value — a gap here costs you rounds
import heapq
import collections
def activity_selection_heap(tasks):
"""Select activities using priority queue"""
freq = collections.Counter(tasks)
heap = [(-count, task) for task, count in freq.items()]
heapq.heapify(heap)
result = []
while heap:
count1, task1 = heapq.heappop(heap)
result.append(task1)
if heap:
count2, task2 = heapq.heappop(heap)
result.append(task2)
if count1 < -1:
heapq.heappush(heap, (count1 + 1, task1))
if count2 < -1:
heapq.heappush(heap, (count2 + 1, task2))
return result
★★★★☆Priority 4 of 5 — High value — a gap here costs you rounds
def greedy_accumulation(prices):
"""Accumulate positive differences (stock trading)"""
profit = 0
for i in range(1, len(prices)):
if prices[i] > prices[i-1]:
profit += prices[i] - prices[i-1]
return profit
★★★★★Priority 5 of 5 — Must know — expect it in almost every loop
def jump_game(nums):
"""Check if can reach end"""
max_reach = 0
for i in range(len(nums)):
if i > max_reach:
return False
max_reach = max(max_reach, i + nums[i])
if max_reach >= len(nums) - 1:
return True
return True
def jump_game_min_jumps(nums):
"""Minimum jumps to reach end"""
jumps = 0
current_end = 0
farthest = 0
for i in range(len(nums) - 1):
farthest = max(farthest, i + nums[i])
if i == current_end:
jumps += 1
current_end = farthest
return jumps
★★★☆☆Priority 3 of 5 — Worth knowing — usually a variant of a must-know pattern
def reorganize_string(s):
"""Reorganize string so no adjacent chars are same"""
from collections import Counter
import heapq
count = Counter(s)
max_count = max(count.values())
if max_count > (len(s) + 1) // 2:
return ""
heap = [(-cnt, char) for char, cnt in count.items()]
heapq.heapify(heap)
result = []
prev_count, prev_char = 0, ''
while heap:
count, char = heapq.heappop(heap)
result.append(char)
if prev_count < 0:
heapq.heappush(heap, (prev_count, prev_char))
prev_count = count + 1
prev_char = char
return ''.join(result)
def fractional_knapsack(items, capacity):
"""Greedy knapsack allowing fractions"""
items.sort(key=lambda x: x[0]/x[1], reverse=True)
total_value = 0
remaining = capacity
for value, weight in items:
if weight <= remaining:
total_value += value
remaining -= weight
else:
total_value += value * (remaining / weight)
break
return total_value
| Problem |
LC # |
Key Technique |
Difficulty |
| Non-overlapping Intervals |
435 |
Sort by end |
Medium |
| Minimum Arrows to Burst Balloons |
452 |
Sort by end |
Medium |
| Maximum Length of Pair Chain |
646 |
Sort by end |
Medium |
| Merge Intervals |
56 |
Sort by start |
Medium |
| Meeting Rooms II |
253 |
Sort + heap |
Medium |
| Interval List Intersections |
986 |
Two pointers |
Medium |
| Problem |
LC # |
Key Technique |
Difficulty |
| Task Scheduler |
621 |
Frequency count |
Medium |
| Maximum Events Attended |
1353 |
Sort + heap |
Medium |
| Course Schedule III |
630 |
Sort + heap |
Hard |
| IPO |
502 |
Two heaps |
Hard |
| Problem |
LC # |
Key Technique |
Difficulty |
| Buy Sell Stock II |
122 |
Accumulate gains |
Easy |
| Gas Station |
134 |
Circular array |
Medium |
| Best Time with Fee |
714 |
State tracking |
Medium |
| Container With Most Water |
11 |
Two pointers |
Medium |
| Problem |
LC # |
Key Technique |
Difficulty |
| Jump Game |
55 |
Track max reach |
Medium |
| Jump Game II |
45 |
Min jumps |
Medium |
| Jump Game III |
1306 |
BFS/DFS |
Medium |
| Reach a Number |
754 |
Math + greedy |
Medium |
| Problem |
LC # |
Key Technique |
Difficulty |
| Reorganize String |
767 |
Max heap |
Medium |
| String Without AAA or BBB |
984 |
Greedy + counter tracking |
Medium |
| Rearrange K Distance Apart |
358 |
Heap + queue |
Hard |
| Task Scheduler |
621 |
Frequency |
Medium |
| Longest Happy String |
1405 |
Heap greedy |
Medium |
| Problem |
LC # |
Key Technique |
Difficulty |
| Candy |
135 |
Two pass |
Hard |
| Assign Cookies |
455 |
Two pointers |
Easy |
| Maximum Units on Truck |
1710 |
Sort by value |
Easy |
| Boats to Save People |
881 |
Two pointers |
Medium |
| Minimum Cost to Connect Sticks |
1167 |
Min heap |
Medium |
| Max Non-Overlapping Subarrays Sum=Target |
1546 |
Prefix sum + greedy reset |
Medium |
| Car Fleet |
853 |
Sort front-first, count arrival-time maxima |
Medium |
| Count Robot Groups |
4045 |
Right-to-left scan, compare speeds (no finish line) |
Medium |
The two above are usually taught as stack problems, and they are not. The
textbook LC 853 solution appends to a stack and never pops, so stack[-1] is
just a running maximum and len(stack) just a counter — two variables do the same
job in O(1) space. Same for LC 4045. A stack only earns its keep in this family at
LC 1776 (Car Fleet II), where an already-computed answer can be invalidated and you
genuinely pop. Full comparison in
monotonic_stack.md.
Interviewers love problems that look greedy. Being able to name the counter-example and pivot is worth as much as the greedy itself.
| Problem |
LC # |
The tempting greedy |
Why it breaks |
What actually works |
| Split Array Largest Sum |
410 |
“Cut whenever the running sum exceeds total/k” |
The threshold isn’t known in advance; a locally-full chunk can force a huge last chunk |
Binary search on the answer + a greedy feasibility check (can we split with max sum <= X using <= k parts?). Greedy is the O(N) validator, not the optimizer. O(N log(sum)) |
| Wildcard Matching |
44 |
“Match chars left-to-right, expand * as needed” |
A * expanded too eagerly can strand a later literal |
Either DP O(S*P), or two-pointer greedy with a backtrack anchor (remember the last * position and rewind on mismatch) |
| Best Time to Buy/Sell with Fee |
714 |
“Sum every positive delta” (LC 122 style) |
The fee is charged per transaction, so tiny rises can be net-negative |
DP state machine hold / cash — see the Stock Trading table above |
| 0/1 Knapsack |
— |
“Sort by value/weight ratio” |
Items can’t be split (counter-example in the Fractional vs 0/1 table) |
DP O(nW) |
Recognition rule of thumb:
- If the objective is “minimize the maximum” / “maximize the minimum” → the greedy usually becomes a monotone predicate inside binary search on the answer, not a standalone algorithm (LC 410 is the canonical case).
- If a choice can be undone profitably later (a fee, a cap, a deadline you can retract) → the fix is often a heap-based “regret” greedy rather than DP. See the greedy with regret template in
heap_advanced.md — LC 871 Minimum Number of Refueling Stops, LC 630 Course Schedule III, LC 1642 Furthest Building You Can Reach.
| Problem |
LC # |
Greedy in one sentence |
Difficulty |
| Valid Palindrome II |
680 |
Two pointers; on the first mismatch, try skipping either side and check the remainder |
Easy |
| Minimum Domino Rotations For Equal Row |
1007 |
Only tops[0] or bottoms[0] can be the target value — test just those two candidates |
Medium |
| Increasing Triplet Subsequence |
334 |
Keep the smallest and second-smallest seen so far; a third beats both ⇒ true |
Medium |
| Largest Number |
179 |
Sort with custom comparator a+b vs b+a (string concat) |
Medium |
| Hand of Straights / Divide Array in Sets of K |
846 / 1296 |
Always start a group from the smallest remaining card |
Medium |
| Minimum Increment to Make Array Unique |
945 |
Sort, then push each element to max(x, prev+1) |
Medium |
| Can Place Flowers |
605 |
Plant at the first legal slot scanning left→right |
Easy |
Greedy Algorithm Selection Flowchart:
1. Can the problem be solved greedily?
├── Does local optimal lead to global optimal? → YES → Use Greedy
├── Can you prove greedy correctness? → YES → Use Greedy
└── NO to both → Use DP or other approach
2. What type of greedy pattern?
├── Selection from sorted items → Interval/Activity Selection
├── Maximize/minimize at each step → Accumulation Pattern
├── Dynamic selection → Priority Queue/Heap
├── Position/reach tracking → Jump Game Pattern
└── Pairing/matching → Two Pointers
3. How to make greedy choice?
├── Sort by what criterion?
│ ├── End time → Interval scheduling
│ ├── Start time → Merge intervals
│ ├── Value/weight ratio → Knapsack
│ └── Custom criterion → Problem specific
└── No sorting needed → Direct iteration
4. Common greedy strategies:
├── Always take the best available
├── Never make a choice that blocks future options
├── Minimize waste/maximize efficiency
└── Balance resources evenly
| Criterion |
Use Greedy |
Use DP |
Example |
| Greedy choice property |
✅ |
❌ |
Activity selection |
| Need all sub-solutions |
❌ |
✅ |
0/1 Knapsack |
| Can prove optimality |
✅ |
- |
Huffman coding |
| Overlapping subproblems |
❌ |
✅ |
Fibonacci |
| Simple selection rule |
✅ |
❌ |
Fractional knapsack |
To verify a greedy choice, show that swapping the greedy pick with any other choice does not improve the result.
1. Assume optimal solution OPT differs from greedy solution G at some step.
2. Show you can swap OPT's choice at that step with G's choice without making things worse.
3. Repeat until OPT == G → greedy is optimal.
Common exchange argument problems: LC 435 (Non-overlapping Intervals), LC 452 (Burst Balloons), Job Scheduling.
Kruskal’s (sort edges, union-find):
def kruskal(n, edges):
edges.sort(key=lambda x: x[2])
parent = list(range(n))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(a, b):
a, b = find(a), find(b)
if a == b: return False
parent[a] = b
return True
mst_cost = 0
for u, v, w in edges:
if union(u, v):
mst_cost += w
return mst_cost
Prim’s (priority queue, dense graphs):
import heapq
from collections import defaultdict
def prim(n, edges):
graph = defaultdict(list)
for u, v, w in edges:
graph[u].append((w, v))
graph[v].append((w, u))
visited = set()
heap = [(0, 0)]
total = 0
while heap and len(visited) < n:
cost, node = heapq.heappop(heap)
if node in visited: continue
visited.add(node)
total += cost
for w, nei in graph[node]:
if nei not in visited:
heapq.heappush(heap, (w, nei))
return total
| Algorithm |
Time |
Best For |
| Kruskal |
O(E log E) |
Sparse graphs |
| Prim (heap) |
O(E log V) |
Dense graphs |
When intervals have weights/profits, greedy alone fails — use DP + binary search.
import bisect
def jobScheduling(startTime, endTime, profit):
jobs = sorted(zip(startTime, endTime, profit), key=lambda x: x[1])
dp = [(0, 0)]
for s, e, p in jobs:
i = bisect.bisect_right(dp, (s, float('inf'))) - 1
new_profit = dp[i][1] + p
if new_profit > dp[-1][1]:
dp.append((e, new_profit))
return dp[-1][1]
| Property |
Fractional |
0/1 |
| Can split items |
Yes |
No |
| Algorithm |
Greedy (sort by value/weight) |
DP |
| Time |
O(n log n) |
O(nW) |
| Greedy works? |
Yes |
No |
Why greedy fails for 0/1: Counter-example: items [(value=6, w=4), (value=5, w=3), (value=5, w=3)], capacity=6. Greedy picks highest ratio (item1, ratio=1.5) → only gets 6. DP picks item2+item3 → gets 10.
| Signal |
Pattern |
| “minimum cost to connect” |
MST (Kruskal/Prim) |
| “maximize non-overlapping intervals” |
Sort by end time |
| “schedule tasks with cooldown” |
Math formula or max-heap |
| “fractional items” |
Sort by value/weight ratio |
| “prove this greedy works” |
Exchange argument |
| “greedy gives wrong answer here” |
Switch to DP |
| Pattern |
Time Complexity |
Space Complexity |
Bottleneck |
| Interval scheduling |
O(nlogn) |
O(1) |
Sorting |
| Heap-based selection |
O(nlogn) |
O(n) |
Heap operations |
| Two pointers |
O(n) or O(nlogn) |
O(1) |
Sorting if needed |
| Direct accumulation |
O(n) |
O(1) |
Single pass |
| Jump game |
O(n) |
O(1) |
Single pass |
intervals.sort(key=lambda x: x[1])
intervals.sort(key=lambda x: x[0])
items.sort(key=lambda x: x.value/x.weight, reverse=True)
tasks.sort(key=lambda x: (x.deadline, -x.profit))
def exchange_argument_proof(arr):
pass
def stays_ahead_proof(greedy, other):
pass
- Identify greedy potential: Look for optimal substructure
- Define greedy choice: What to select at each step
- Prove correctness: Exchange argument or stays ahead
- Implement efficiently: Often requires sorting
- Handle edge cases: Empty input, single element
- Verify with examples: Test greedy choices
🚫 Common Mistakes:
- Assuming greedy works without proof
- Wrong sorting criterion
- Not considering all edge cases
- Forgetting to handle ties
- Missing global constraint checks
✅ Best Practices:
- Always verify greedy property first
- Start with small examples
- Consider counter-examples
- Use heap for dynamic selection
- Test with edge cases
- Recognize patterns: Look for sorting or selection hints
- Start with examples: Work through small cases
- State assumptions: Clarify if greedy is applicable
- Prove if asked: Use exchange or stays ahead
- Code cleanly: Greedy code is usually simple
- Optimize: Consider using heap for better complexity
- Activity Selection: Choose maximum non-overlapping
- Huffman Coding: Build optimal prefix codes
- Kruskal’s MST: Select minimum weight edges
- Dijkstra’s: Select minimum distance vertex
- Fractional Knapsack: Take most valuable ratio
- Dynamic Programming: When greedy doesn’t work
- Binary Search: For optimization problems
- Heap/Priority Queue: For dynamic selection
- Sorting: Often prerequisite for greedy
- Graph Algorithms: Many use greedy (MST, shortest path)
Fourteen problems live in greedy_examples.md, grouped by the shape
of the greedy choice rather than by topic — which is what you actually have to recognise:
| Group |
The choice you make |
Problems |
| Reach & jump |
extend the furthest reachable point, and only jump when you must |
LC 55, 45, 1326 |
| Accumulate & reset |
take every gain; reset a running total the moment it goes negative |
LC 122, 134, 1546, 921 |
| Frequency & heap interleaving |
always place the most frequent item that is currently legal |
LC 767, 984, 621 |
| Sort, then take |
sort by the ratio that matters, then consume in order |
LC 1710, 3994 |
| Build while scanning |
commit a character or a boundary as soon as it can no longer change |
LC 763, 402 |