252. Meeting Rooms π
Problem Description
You are given an array of meeting time intervals, where each interval is represented as intervals[i] = [starti, endi]
. Each interval contains a start time and an end time for a meeting. Your task is to determine whether a person can attend all the meetings without any time conflicts.
A person can attend all meetings if no two meetings overlap in time. Two meetings overlap if one meeting starts before the other meeting ends.
For example:
- If you have meetings
[0, 30]
and[5, 10]
, these overlap because the second meeting starts at time 5, which is before the first meeting ends at time 30. The person cannot attend both meetings. - If you have meetings
[0, 30]
and[35, 50]
, these don't overlap because the second meeting starts after the first one ends. The person can attend both meetings.
The function should return true
if the person can attend all meetings (no overlaps exist), and false
if there are any time conflicts between meetings.
Intuition
To check if a person can attend all meetings, we need to ensure no two meetings overlap. The key insight is that if we arrange the meetings in chronological order by their start times, we can simplify the overlap detection process.
Think about how you would manually check your calendar for conflicts. You'd likely look at meetings in the order they occur throughout the day. If any meeting starts before the previous one ends, you know there's a conflict.
By sorting the intervals by start time first, we create a natural timeline of meetings. Once sorted, checking for overlaps becomes straightforward - we only need to compare adjacent meetings. For any two consecutive meetings in the sorted list, if the first meeting ends after the second meeting starts (i.e., end_time_of_first > start_time_of_second
), we have an overlap.
The beauty of this approach is that after sorting, we don't need to check every pair of meetings. We only check adjacent pairs because:
- If meeting A comes before meeting B in sorted order (A starts earlier than B)
- And if A doesn't overlap with B
- Then A cannot overlap with any meeting that comes after B either (since those meetings start even later)
This reduces our problem from checking all possible pairs O(nΒ²)
to just checking adjacent pairs O(n)
after sorting. The condition for no overlap between adjacent meetings is simply: the end time of the earlier meeting must be less than or equal to the start time of the next meeting (a[1] <= b[0]
).
Solution Approach
The implementation follows a sorting-based approach to detect meeting conflicts:
-
Sort the intervals: We first sort all meeting intervals based on their start times using
intervals.sort()
. Since each interval is represented as[start, end]
, Python's default sorting will compare the first element (start time) of each interval, giving us meetings arranged chronologically. -
Check adjacent meetings for overlaps: After sorting, we use the
pairwise
function to iterate through consecutive pairs of meetings. Thepairwise(intervals)
function creates pairs like(intervals[0], intervals[1])
,(intervals[1], intervals[2])
, and so on. -
Overlap detection logic: For each pair of adjacent meetings
(a, b)
wherea
comes beforeb
in the sorted order:a[1]
represents the end time of the first meetingb[0]
represents the start time of the second meeting- The condition
a[1] <= b[0]
checks if the first meeting ends before or exactly when the second meeting starts - If this condition is true for all pairs, there are no overlaps
-
Return the result: The
all()
function returnsTrue
if all adjacent pairs satisfy the non-overlapping condition, meaning the person can attend all meetings. If even one pair overlaps (condition returnsFalse
), theall()
function returnsFalse
.
Time Complexity: O(n log n)
where n
is the number of intervals, dominated by the sorting step.
Space Complexity: O(1)
if we don't count the space used by the sorting algorithm (typically O(log n)
for the recursion stack in sorting).
The elegance of this solution lies in its use of Python's built-in functions - sort()
for ordering, pairwise()
for creating adjacent pairs, and all()
for checking the condition across all pairs in a single, readable line.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example to see how it detects meeting conflicts.
Example Input: intervals = [[7, 10], [2, 4], [8, 12]]
Step 1: Sort the intervals by start time
- Before sorting:
[[7, 10], [2, 4], [8, 12]]
- After sorting:
[[2, 4], [7, 10], [8, 12]]
- The meetings are now arranged chronologically by when they begin
Step 2: Create pairs of adjacent meetings using pairwise
- The
pairwise
function generates:- Pair 1:
([2, 4], [7, 10])
- Pair 2:
([7, 10], [8, 12])
- Pair 1:
Step 3: Check each pair for overlaps
-
Pair 1:
[2, 4]
and[7, 10]
- Does meeting 1 end before meeting 2 starts?
- Check:
4 <= 7
βTrue
β (No overlap)
-
Pair 2:
[7, 10]
and[8, 12]
- Does meeting 1 end before meeting 2 starts?
- Check:
10 <= 8
βFalse
β (Overlap detected!) - Meeting at
[7, 10]
ends at time 10, but the next meeting[8, 12]
starts at time 8 - These meetings overlap from time 8 to 10
Step 4: Return the result
- Since not all pairs satisfy the non-overlapping condition,
all()
returnsFalse
- The person cannot attend all meetings due to the conflict between
[7, 10]
and[8, 12]
Contrasting Example (No conflicts):
If we had intervals = [[0, 5], [10, 15], [20, 25]]
:
- Already sorted:
[[0, 5], [10, 15], [20, 25]]
- Check pairs:
5 <= 10
βTrue
β15 <= 20
βTrue
β
- Result:
True
- person can attend all meetings
Solution Implementation
1class Solution:
2 def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
3 # Sort intervals by start time (first element of each interval)
4 intervals.sort()
5
6 # Check if any two consecutive meetings overlap
7 # For each pair of consecutive intervals, verify that the first meeting ends
8 # before or at the same time the second meeting starts
9 for i in range(len(intervals) - 1):
10 current_meeting = intervals[i]
11 next_meeting = intervals[i + 1]
12
13 # If current meeting's end time is after next meeting's start time,
14 # there is an overlap (cannot attend both)
15 if current_meeting[1] > next_meeting[0]:
16 return False
17
18 # No overlaps found, can attend all meetings
19 return True
20```
21
22Alternative implementation using the pairwise approach from itertools:
23
24```python3
25from itertools import pairwise
26from typing import List
27
28class Solution:
29 def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
30 # Sort intervals by start time (first element of each interval)
31 intervals.sort()
32
33 # Check all consecutive pairs of meetings
34 # Verify that each meeting ends before or when the next one starts
35 # all() returns True if all conditions are met or if the list is empty
36 return all(first[1] <= second[0] for first, second in pairwise(intervals))
37
1class Solution {
2 /**
3 * Determines if a person can attend all meetings without any time conflicts.
4 *
5 * @param intervals 2D array where each element is [startTime, endTime] of a meeting
6 * @return true if all meetings can be attended (no overlaps), false otherwise
7 */
8 public boolean canAttendMeetings(int[][] intervals) {
9 // Sort meetings by start time in ascending order
10 Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
11
12 // Check for overlapping meetings
13 for (int i = 1; i < intervals.length; i++) {
14 // If previous meeting's end time is after current meeting's start time,
15 // there is an overlap
16 if (intervals[i - 1][1] > intervals[i][0]) {
17 return false;
18 }
19 }
20
21 // No overlaps found, all meetings can be attended
22 return true;
23 }
24}
25
1class Solution {
2public:
3 bool canAttendMeetings(vector<vector<int>>& intervals) {
4 // Sort intervals by start time in ascending order
5 sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
6 return a[0] < b[0];
7 });
8
9 // Check for overlapping intervals
10 // If the end time of previous interval is greater than start time of current interval,
11 // there is an overlap and person cannot attend all meetings
12 for (int i = 1; i < intervals.size(); ++i) {
13 if (intervals[i - 1][1] > intervals[i][0]) {
14 return false; // Found overlapping meetings
15 }
16 }
17
18 // No overlaps found, can attend all meetings
19 return true;
20 }
21};
22
1/**
2 * Determines if a person can attend all meetings without time conflicts
3 * @param intervals - Array of meeting intervals where each interval is [startTime, endTime]
4 * @returns true if all meetings can be attended, false if there are conflicts
5 */
6function canAttendMeetings(intervals: number[][]): boolean {
7 // Sort intervals by start time in ascending order
8 intervals.sort((a: number[], b: number[]) => a[0] - b[0]);
9
10 // Check for overlapping intervals
11 for (let i: number = 1; i < intervals.length; i++) {
12 // If current meeting starts before previous meeting ends, there's a conflict
13 if (intervals[i][0] < intervals[i - 1][1]) {
14 return false;
15 }
16 }
17
18 // No conflicts found, all meetings can be attended
19 return true;
20}
21
Time and Space Complexity
Time Complexity: O(n Γ log n)
The time complexity is dominated by the sorting operation intervals.sort()
, which takes O(n Γ log n)
time where n
is the number of intervals (meetings). After sorting, the code iterates through adjacent pairs using pairwise()
and checks if each meeting ends before the next one starts with a[1] <= b[0]
. This iteration and comparison takes O(n)
time. Therefore, the overall time complexity is O(n Γ log n) + O(n) = O(n Γ log n)
.
Space Complexity: O(log n)
The space complexity comes from the sorting algorithm. Python's built-in sort()
uses Timsort, which requires O(log n)
space for the recursion stack in the worst case. The pairwise()
function creates an iterator that only holds references to consecutive pairs at a time, using O(1)
additional space. The all()
function also uses O(1)
space as it evaluates the generator expression lazily. Therefore, the overall space complexity is O(log n)
.
Common Pitfalls
1. Confusing Overlap Conditions
One of the most common mistakes is incorrectly defining when two meetings overlap. Students often write:
# INCORRECT - This misses edge cases if current_meeting[1] >= next_meeting[0]: # Wrong comparison return False
Why it's wrong: Using >=
means that meetings like [1, 2]
and [2, 3]
would be considered overlapping, but they're not! A meeting ending at time 2 and another starting at time 2 means one person can attend both (one ends exactly when the other begins).
Correct approach:
# CORRECT - Meetings overlap only if one ends AFTER the other starts if current_meeting[1] > next_meeting[0]: return False
2. Forgetting to Sort
Another critical mistake is checking for overlaps without sorting first:
# INCORRECT - Checking unsorted intervals
def canAttendMeetings(self, intervals):
for i in range(len(intervals) - 1):
if intervals[i][1] > intervals[i + 1][0]:
return False
return True
Why it's wrong: Without sorting, you're only checking adjacent meetings in the input order, not chronological order. For example, with input [[7, 10], [2, 4]]
, this would incorrectly return True
because it only checks if [7, 10]
overlaps with [2, 4]
as given, missing that chronologically [2, 4]
comes first.
Solution: Always sort by start time first:
intervals.sort() # Critical step!
3. Checking All Pairs Instead of Adjacent Pairs
Some implementations unnecessarily check every possible pair of meetings:
# INEFFICIENT - O(nΒ²) time complexity
for i in range(len(intervals)):
for j in range(i + 1, len(intervals)):
if intervals[i][1] > intervals[j][0] and intervals[j][1] > intervals[i][0]:
return False
Why it's inefficient: After sorting, you only need to check consecutive meetings. If meeting A doesn't overlap with meeting B, and B doesn't overlap with C, then A definitely won't overlap with C (since they're sorted by start time).
Optimal approach: Only check adjacent pairs after sorting for O(n log n) complexity instead of O(nΒ²).
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!