Pattern: Sort by end time, greedily keep non-overlapping intervals.
When to recognize: Problem involves intervals, meetings, tasks with start/end times.
intervals.sort(key=lambda x: x[1]) # sort by end time
prev_end = float('-inf')
for start, end in intervals:
if start >= prev_end:
prev_end = end # keep it
else:
count += 1 # discard it
Pattern: Start pointers at both ends, move the "worse" side inward.
When to recognize: Maximize/minimize something involving pairs from opposite ends.
left, right = 0, len(arr) - 1
while left < right:
result = max(result, some_function(arr[left], arr[right]))
if arr[left] <= arr[right]:
left += 1
else:
right -= 1
Pattern: Track the furthest reachable index, jump only when forced.
When to recognize: Can you reach the end? Minimum jumps to reach end?
farthest = 0
curr_end = 0
jumps = 0
for i in range(len(nums) - 1):
farthest = max(farthest, i + nums[i])
if i == curr_end:
jumps += 1
curr_end = farthest
Pattern: Sort by some key that makes the greedy choice obvious, then iterate.
When to recognize: Optimization problem where ordering matters.
# sort by whatever makes the greedy choice locally optimal
items.sort(key=lambda x: x[0])
for item in items:
# make greedy choice