The Quest Begins (The "Why")
I remember the first time I saw an interview question about “maximum number of non‑overlapping meetings.” My brain instantly went into brute‑force mode: what if I try every subset? I wrote a recursive backtracking solution that explored 2ⁿ possibilities, watched the test cases timeout, and felt like I was trying to defeat a Death Star with a blaster pistol. Frustrating, right?
The problem kept popping up in mock interviews, on LeetCode, and even in a casual chat with a friend who swore there was a “trick” that made it trivial. I was skeptical—how could picking a meeting at random possibly lead to the best answer? I spent a few nights staring at whiteboards, drawing intervals, and wondering if I was missing some hidden pattern. That’s when the quest for a truly greedy insight began.
The Revelation (The Insight)
Here’s the treasure: if you always pick the activity that finishes earliest, you can never hurt your chances of fitting in more activities later. It sounds almost too simple, but there’s a neat exchange argument that proves it.
Imagine you have an optimal solution OPT that doesn’t start with the earliest‑finishing activity A. Let B be the first activity in OPT. Since A finishes no later than B, we can replace B with A and still have a feasible schedule (because A ends earlier, it leaves at least as much room for the rest). The new schedule has the same size as OPT but now starts with the greedy choice. Repeating this argument shows there’s an optimal solution that begins with the greedy pick, and after removing that activity the same reasoning applies to the remainder.
In plain English: the earliest‑finishing activity is a safe move—it never blocks a better future. Once you make that safe move, the problem shrinks to the same type on the remaining intervals, so you can repeat the step. That’s why the greedy strategy yields an optimal solution.
It felt like when Neo dodges bullets in the Matrix—you realize the system has a simple rule that lets you see the whole battlefield instantly.
Wielding the Power (Code & Examples)
The Struggle: Brute Force
def max_activities_brute(starts, ends):
n = len(starts)
best = 0
def dfs(idx, last_end, count):
nonlocal best
if idx == n:
best = max(best, count)
return
# skip current
dfs(idx + 1, last_end, count)
# take current if it doesn't clash
if starts[idx] >= last_end:
dfs(idx + 1, ends[idx], count + 1+1, last_end, count)
dfs(0, -float('inf'), 0)
return best
The above explores every subset (O(2ⁿ)) and times out on anything beyond ~20 intervals.
The Victory: Greedy
def max_activities_greedy(starts, ends):
# 1️⃣ Pair and sort by finishing time (the Jedi move)
activities = sorted(zip(starts, ends), key=lambda x: x[1])
count = 0
last_end = -float('inf')
for s, e in activities:
if s >= last_end: # 2️⃣ Take if it doesn’t overlap
count += 1
last_end = e # 3️⃣ Update the barrier
return count
Why it’s O(n log n): The sorting step dominates; the subsequent scan is linear. If the input is already sorted by end time, the algorithm is pure O(n).
Common trap #1 – Sorting by start time
If you sort by start time instead, you might pick a long activity that blocks many short ones that finish earlier. The greedy proof only works with the earliest finishing rule.
Common trap #2 – Forgetting to update last_end
Neglecting to move the barrier forward lets you count overlapping activities, inflating the answer incorrectly.
Interview Problem #1 – Classic Activity Selection
Given start and end times of *n meetings, return the maximum number of meetings that can be attended without overlap.*
Solution: Directly use max_activities_greedy.
Interview Problem #2 – Meeting Rooms (LeetCode #252)
Determine if a person can attend all meetings (i.e., no two overlap).
After sorting by start time, you only need to check neighboring pairs:
def can_attend_meetings(intervals):
intervals.sort(key=lambda x: x[0]) # sort by start
for i in range(1, len(intervals)):
if intervals[i][0] < intervals[i-1][1]: # overlap
return False
return True
The same sorting idea appears; the inner check is O(n), so overall O(n log n).
Why This New Power Matters
Armed with this greedy insight, you can slash interview time from exponential to linearithmic, impress interviewers with a clean proof, and tackle real‑world scheduling problems—think CPU task sequencing, conference room booking, or even allocating ad slots.
More importantly, you’ve added a tool to your problem‑solving belt: look for a safe, locally optimal choice that leaves the subproblem unchanged in structure. Whenever you see a “pick one, then repeat” pattern, ask: does an exchange argument exist? If yes, you’ve likely found a greedy solution.
Your Turn
Grab a scrap of paper, draw a handful of intervals, and try the earliest‑finish rule by hand. Then code it up and test against random cases. Notice how the greedy answer always matches the brute‑force optimum for small n.
When you feel comfortable, throw in a twist—maybe each activity has a weight, or you need to minimize the number of resources instead of maximizing count. See where the greedy proof breaks and where you need to switch to DP or flow.
Challenge: Implement the greedy solution for “Maximum number of non‑overlapping intervals” in a language you don’t use daily (Rust, Go, or even Bash with sort and awk). Share your snippet in the comments—I’d love to see your Jedi code in action!
May the Force be with your intervals. 🚀
Top comments (0)