Movie Festival
Problem Overview
| Aspect | Details |
|---|---|
| Problem Type | Interval Scheduling / Activity Selection |
| Technique | Greedy Algorithm |
| Time Complexity | O(n log n) |
| Space Complexity | O(1) |
| Key Operation | Sort by end time, greedy selection |
Learning Goals
By completing this problem, you will learn:
- The classic interval scheduling problem pattern
- Why sorting by end time leads to the optimal greedy solution
- How greedy algorithms can achieve optimal solutions when the greedy choice property holds
Problem Statement
Given n movies with start and end times, find the maximum number of movies you can watch without overlapping. You can only watch one movie at a time.
Input:
- First line: integer n (1 <= n <= 2x10^5)
- Next n lines: two integers a and b (start and end time, 1 <= a <= b <= 10^9)
Output: Maximum number of non-overlapping movies
Example:
Input:
3
3 5
4 9
5 8
Output:
2
Key Insight
Sort movies by END time, then greedily take the earliest-ending movie that doesn’t conflict with your last selection.
This greedy approach is provably optimal for the activity selection problem.
Why Sort by End Time?
Sorting by end time leaves maximum room for future movies.
When you pick a movie that ends early, you maximize the remaining time window for subsequent movies. This is the essence of the greedy choice property.
Intuition: Imagine you’re filling a timeline. The earlier a movie ends, the more space remains for other movies.
Timeline: |---------------------------------------------->
Movie A (ends early): [====]
^-- lots of room left
Movie B (ends late): [================]
^-- less room left
Algorithm
- Sort all movies by their end time (ascending)
- Initialize
last_end = 0(tracks when your last movie ended) - For each movie in sorted order:
- If
start >= last_end: take this movie, updatelast_end = end - Otherwise: skip (conflicts with previous selection)
- If
- Return the count of movies taken
Visual Diagram
Original movies:
Movie 1: [3----5]
Movie 2: [4---------9]
Movie 3: [5----8]
After sorting by END time:
Movie 1: [3----5] (ends at 5)
Movie 3: [5----8] (ends at 8)
Movie 2: [4---------9] (ends at 9)
Greedy Selection:
Step 1: last_end = 0
Movie 1 starts at 3 >= 0? YES -> Take it, last_end = 5
Step 2: last_end = 5
Movie 3 starts at 5 >= 5? YES -> Take it, last_end = 8
Step 3: last_end = 8
Movie 2 starts at 4 >= 8? NO -> Skip
Result: 2 movies selected [Movie 1, Movie 3]
Dry Run
| Step | Movie (start, end) | last_end | start >= last_end? | Action | Count |
|---|---|---|---|---|---|
| 0 | - | 0 | - | Initialize | 0 |
| 1 | (3, 5) | 0 | 3 >= 0 = YES | Take, last_end = 5 | 1 |
| 2 | (5, 8) | 5 | 5 >= 5 = YES | Take, last_end = 8 | 2 |
| 3 | (4, 9) | 8 | 4 >= 8 = NO | Skip | 2 |
Final Answer: 2
Python Solution
def solve():
n = int(input())
movies = []
for _ in range(n):
a, b = map(int, input().split())
movies.append((a, b))
# Sort by end time
movies.sort(key=lambda x: x[1])
count = 0
last_end = 0
for start, end in movies:
if start >= last_end:
count += 1
last_end = end
print(count)
solve()
Why Not Sort by Start Time or Duration?
Counter-example: Sorting by Start Time
Movies: (1, 100), (2, 3), (4, 5)
Sort by start time: (1, 100), (2, 3), (4, 5)
Greedy result: Take (1, 100) -> blocks everything else
Result: 1 movie
Optimal (sort by end): (2, 3), (4, 5), (1, 100)
Take (2, 3), then (4, 5)
Result: 2 movies
Sorting by start time fails because an early-starting movie might span a long time, blocking many shorter movies.
Counter-example: Sorting by Duration
Movies: (1, 3), (2, 4), (3, 5)
Durations: 2, 2, 2 (all same)
Any order might work, but consider:
Movies: (1, 2), (1, 10), (2, 3)
Durations: 1, 9, 1
Sort by duration: (1, 2), (2, 3), (1, 10)
Take (1, 2), then (2, 3) -> 2 movies (happens to work)
But: (0, 5), (1, 3), (3, 6)
Durations: 5, 2, 3
Sort by duration: (1, 3), (3, 6), (0, 5)
Take (1, 3), then (3, 6) -> 2 movies
Sort by end: (1, 3), (0, 5), (3, 6)
Take (1, 3), then (3, 6) -> 2 movies
The issue: (0, 3), (2, 5), (4, 7), (1, 8)
Durations: 3, 3, 3, 7
Sort by duration: (0, 3), (2, 5), (4, 7), (1, 8)
Take (0, 3), skip (2, 5), take (4, 7) -> 2 movies
But (1, 8) with duration 7 could block good intervals.
Key insight: Duration doesn’t account for where the movie sits on the timeline. End time directly controls how much timeline remains.
Common Mistakes
1. Sorting by Start Time Instead of End Time
# WRONG
movies.sort(key=lambda x: x[0]) # Sort by start time
# CORRECT
movies.sort(key=lambda x: x[1]) # Sort by end time
2. Using Strict Inequality Instead of >=
# WRONG - misses back-to-back movies
if start > last_end:
# CORRECT - allows movie starting exactly when previous ends
if start >= last_end:
A movie can start at the exact moment another ends. If Movie A ends at time 5, Movie B can start at time 5.
3. Not Handling Large Inputs Efficiently
- Use fast I/O in C++ (
ios::sync_with_stdio(false)) - Avoid unnecessary data structures
- The algorithm is O(n log n) due to sorting; the greedy selection is O(n)
Complexity Analysis
| Operation | Time Complexity |
|---|---|
| Sorting | O(n log n) |
| Greedy Selection | O(n) |
| Total | O(n log n) |
Space Complexity: O(1) extra space (or O(n) for storing movies)