Maximum Number of Events That Can Be Attended
Table of Contents + β
This problem looks like a calendar puzzle. You have events with start and end days. You can attend only one event per day. You want to attend as many events as you can. The smart move is to handle each day in order and always attend the event that ends soonest. A min-heap keeps that soonest-ending event in reach. That pairing of greedy plus heap is what the interviewer is checking.
π― The Problem
You get a list of events. Each one has a start day and an end day. Here are the rules.
- Each event has a start day and an end day.
- You may attend an event on any single day in its range, start to end.
- You may attend only one event on any given day.
- You want the largest number of events you can attend.
Look at [[1,2],[2,3],[3,4]]. On day 1 attend the first event. On day 2 attend the second event. On day 3 attend the third event. So you attend all three.
Input: events = [[1,2],[2,3],[3,4]]Output: 3
Explanation: Day 1 -> attend event [1,2] Day 2 -> attend event [2,3] Day 3 -> attend event [3,4]The danger is events that overlap. Overlap means their day ranges cross. When two events are both open on the same day, you can pick only one. So you must choose wisely.
The picture below shows the three events on a day line.
π’ Approach 1: Try Every Choice (Brute Force)
The idea in one line: search every way of picking events.
The idea:
- For each day, try attending each open event.
- Then explore what happens after that pick.
- Back up when a path runs out. Try another.
How it works:
- This is a full search over all combinations.
- It does find the right answer.
Why it is weak:
- The number of paths grows extremely fast.
- This is exponential time.
- It crashes on more than a handful of events.
Here is the backtracking code:
def max_events(events): events.sort()
def dfs(index, used_days): if index == len(events): return 0 best = dfs(index + 1, used_days) start, end = events[index] for day in range(start, end + 1): if day not in used_days: used_days.add(day) best = max(best, 1 + dfs(index + 1, used_days)) used_days.remove(day) return best
return dfs(0, set())π Approach 2: Greedy On a Day Array (Better)
The idea in one line: book each event into the earliest free day in its range.
The idea:
- Sort events by end day, soonest end first.
- For each event, walk its days from start to end.
- Take the first day that is still free. Mark it used.
How it works:
- The soonest-ending event is the most urgent. Place it first.
- A boolean array remembers which days are taken.
Why it is better:
- It beats brute force by a wide margin.
- But scanning every day of every event is wasteful.
- On a wide day range this becomes slow.
Here is the day-array greedy code:
def max_events(events): events.sort(key=lambda item: item[1]) last_day = max(end for start, end in events) used = [False] * (last_day + 1) answer = 0
for start, end in events: for day in range(start, end + 1): if not used[day]: used[day] = True answer += 1 break
return answerβ‘ Approach 3: Greedy Day by Day With a Min-Heap (Best)
The idea in one line: walk day by day, and each day attend the open event that ends soonest.
The idea:
- Move through days from earliest to latest.
- Each day, look at events that have started and are not finished.
- Attend the one that ends soonest. This is earliest deadline first.
Why ends soonest:
- That event has the least time left.
- Skip it now and you may never get another chance.
- An event ending later still has spare days. So save the urgent one.
How it works:
- A min-heap always hands you the smallest item first. Here smallest means earliest end day.
- Sort the events by start day first.
- Each day, push every event that starts on or before that day.
- Drop any heap-top event whose end day already passed.
- If the heap is not empty, pop the top and attend it. Count it.
Why it is fast:
- Each event is pushed and popped once.
- The heap steps cost O(log n) each. So the whole run is O(n log n).
The diagram below shows the day-by-day loop.
Steps to Solve
- Sort the events by their start day.
- Keep a min-heap of end days for events that have started and are still open.
- Walk through days in order. On the current day, push the end day of every event whose start day has arrived.
- Remove from the top of the heap any event whose end day is before the current day. It is too late for that one.
- If the heap is not empty, pop the smallest end day. Attend that event. Add one to the count.
- Move to the next day. Stop when no events are left and the heap is empty. Return the count.
This Python version sorts by start day and uses heapq, which is a min-heap, to hold the end days of open events.
import heapq
def max_events(events): events.sort() # sort by start day heap = [] # min-heap of end days count = 0 i = 0 n = len(events) day = 0
while i < n or heap: if not heap: day = events[i][0] # jump to next event start while i < n and events[i][0] <= day: heapq.heappush(heap, events[i][1]) # event has started i += 1 while heap and heap[0] < day: heapq.heappop(heap) # this event already ended if heap: heapq.heappop(heap) # attend the soonest-ending event count += 1 day += 1
return count
events = [[1, 2], [2, 3], [3, 4]]print(max_events(events)) # 3The output of the above code will be:
3Let us walk through the Python version line by line, because the day-by-day greedy is easy to get slightly wrong.
The line events.sort() sorts events by start day. Sorting by start lets us add events to the heap in order, as their start day arrives.
The line heap = [] is the min-heap of end days. We store only the end day, since that is what tells us how urgent an event is.
The main loop runs while i < n or heap:. So it keeps going while there are events we have not added yet, or open events still in the heap.
The line if not heap: day = events[i][0] jumps the day forward when nothing is open. There is no point sitting on empty days. We skip straight to the next eventβs start.
The first inner loop, while i < n and events[i][0] <= day:, pushes every event whose start day has arrived. After the push it is βopenβ and waiting in the heap.
The second inner loop, while heap and heap[0] < day:, throws away events whose end day already passed. heap[0] is the smallest end day. If even that one ended before today, it is too late, so we drop it.
The line if heap: then attends the best event. heappop removes the soonest-ending event, which is the most urgent. We add one to count. Then day += 1 moves to the next day.
β±οΈ Time and Space Complexity
The brute force explores all choices, so it is exponential and not usable. The greedy version sorts once, which is O(n log n). Then each event is pushed and popped from the heap at most once, and each heap step is O(log n). So the total is O(n log n). The space is O(n) for the heap in the worst case, when many events are open at the same time.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Try every choice (search) | Exponential | O(n) |
| Greedy on a day array | O(n Β· D) | O(D) |
| Greedy with min-heap | O(n log n) | O(n) |
Tip
The greedy reason is simple. On a busy day, attend the event that ends soonest. The events that end later still have other days to spare. Saying that one sentence to the interviewer proves the greedy choice is safe.
π§© Key Takeaways
- β Sort events by start day so you can add them as their day arrives.
- β Walk day by day. Keep open events in a min-heap of end days.
- β Each day, attend the event that ends soonest. That is earliest deadline first.
- β Drop events whose end day already passed before you pick one.
- β The whole thing is O(n log n), driven by the sort and the heap steps.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
On a busy day with several open events, which one should you attend?
Why: The event that ends soonest is the most urgent. Events ending later still have spare days, so you save the urgent one.
- 2
What does the min-heap store in the optimal solution?
Why: The heap holds the end days of currently open events, so the smallest end day is always on top.
- 3
Why do we sort the events by start day first?
Why: Sorting by start lets us push events into the heap in order, exactly when they become available.
- 4
What is the overall time complexity of the greedy heap solution?
Why: Sorting is O(n log n) and each event is pushed and popped once with O(log n) heap steps, so the total is O(n log n).