Skip to content

253. Meeting Rooms II (Medium)

Problem

Given an array of meeting time intervals, return the minimum number of conference rooms required.

Example

  • intervals = [[0,30],[5,10],[15,20]]2
  • intervals = [[7,10],[2,4]]1

LeetCode 253 (premium) · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, simulate time ticks

For every time t from 0 to max_end, count active meetings. Max over all t is the answer.

def min_meeting_rooms(intervals):
if not intervals:
return 0
max_end = max(e for _, e in intervals)
best = 0
for t in range(max_end + 1): # L1: T iterations
active = sum(1 for s, e in intervals if s <= t < e) # L2: O(n) per tick
best = max(best, active)
return best

Direct but quadratic in (T · n). Useful as a sanity-check oracle, never as a real solution.

Complexity

  • Time: O(Tn)O(T · n). Infeasible on big time ranges.
  • Space: O(1)O(1).

Approach 2: Min-heap of end times (canonical)

Sort intervals by start. Maintain a min-heap of end times for current rooms. For each new meeting: pop end times ≤ current start (those rooms freed up); push the new end time. Answer = max heap size.

import heapq
def min_meeting_rooms(intervals):
intervals.sort(key=lambda x: x[0]) # L1: O(n log n)
heap = [] # L2: O(1)
for s, e in intervals: # L3: outer loop, n iterations
if heap and heap[0] <= s: # L4: O(log n) peek
heapq.heappop(heap) # L5: O(log n) free a room
heapq.heappush(heap, e) # L6: O(log n) book a room
return len(heap) # L7: O(1)

Where the time goes, line by line

Variables: n = len(intervals).

LinePer-call costTimes executedContribution
L1 (sort)O(nlogn)O(n log n)1O(nlogn)O(n log n)
L2 (init heap)O(1)O(1)1O(1)O(1)
L3 (loop)O(1)O(1)nO(n)O(n)
L4 (peek)O(1)O(1)nO(n)O(n)
L5 (heappop)O(logn)O(log n)at most nO(nlogn)O(n log n)
L6 (heappush)O(logn)O(log n)nO(nlogn)O(n log n) ← dominates
L7 (len)O(1)O(1)1O(1)O(1)

L1 (sort) and L6 (n pushes) both contribute O(nlogn)O(n log n); neither strictly dominates the other, they tie. L5 pops at most n times total across the entire loop, not once per iteration, because each interval is pushed once and popped at most once.

Complexity

  • Time: O(nlogn)O(n log n), driven by L1 (sort) and L6 (heap pushes, n total).
  • Space: O(n)O(n) for the heap in the worst case (all meetings overlap).

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 3: Sweep line with separate start/end arrays (counters only)

Sort starts and ends independently. Two pointers; increment count on a start, decrement on an end (before a new start). Track max count.

def min_meeting_rooms(intervals):
starts = sorted(s for s, _ in intervals) # L1: O(n log n)
ends = sorted(e for _, e in intervals) # L2: O(n log n)
i = j = 0 # L3: O(1)
used = best = 0 # L4: O(1)
while i < len(intervals): # L5: outer loop, n iterations
if starts[i] < ends[j]: # L6: O(1) comparison
used += 1 # L7: O(1)
best = max(best, used) # L8: O(1)
i += 1 # L9: O(1)
else:
used -= 1 # L10: O(1)
j += 1 # L11: O(1)
return best # L12: O(1)

Where the time goes, line by line

Variables: n = len(intervals).

LinePer-call costTimes executedContribution
L1 (sort starts)O(nlogn)O(n log n)1O(nlogn)O(n log n) ← dominates
L2 (sort ends)O(nlogn)O(n log n)1O(nlogn)O(n log n) ← dominates
L3, L4 (init)O(1)O(1)1O(1)O(1)
L5 (loop test)O(1)O(1)nO(n)O(n)
L6-L11 (body)O(1)O(1)nO(n)O(n)
L12 (return)O(1)O(1)1O(1)O(1)

After sorting, the while loop runs exactly n times (i advances once per iteration until i == n). Every comparison and counter update is O(1)O(1), so the loop contributes only O(n)O(n). All the cost lives in the two sorts.

Complexity

  • Time: O(nlogn)O(n log n), driven by L1 and L2 (the two sorts).
  • Space: O(n)O(n) for the two auxiliary sorted arrays.

Why it works

Intuitively: whenever a meeting’s start comes before the earliest current end, we need a new room. Whenever an end arrives first, a room frees up. The running count of active rooms is exactly what we want.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

ApproachTimeSpace
Brute time tickO(Tn)O(T · n)O(1)O(1)
Heap of end timesO(nlogn)O(n log n)O(n)O(n)
Sweep-line countersO(nlogn)O(n log n)O(n)O(n)

Heap version is the canonical answer. Sweep-line is the shortest.

Test cases

# Quick smoke tests, paste into a REPL or save as test_253.py and run.
# Uses the canonical implementation (Approach 2: min-heap of end times).
import heapq
def min_meeting_rooms(intervals):
intervals.sort(key=lambda x: x[0])
heap = []
for s, e in intervals:
if heap and heap[0] <= s:
heapq.heappop(heap)
heapq.heappush(heap, e)
return len(heap)
def _run_tests():
assert min_meeting_rooms([[0,30],[5,10],[15,20]]) == 2 # example 1
assert min_meeting_rooms([[7,10],[2,4]]) == 1 # example 2: no overlap
assert min_meeting_rooms([[1,5]]) == 1 # single meeting
assert min_meeting_rooms([]) == 0 # empty
assert min_meeting_rooms([[1,4],[2,5],[3,6]]) == 3 # all overlap
assert min_meeting_rooms([[0,5],[5,10],[10,15]]) == 1 # touching endpoints, sequential
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Difference Arrays, range-update tactics that mark changes at boundaries and recover final values with a prefix scan.
  • Intervals, range-boundary tactics for overlap, containment, scheduling, and sweep-line problems.