2402. Meeting Rooms III
Problem Description
You have n
meeting rooms numbered from 0
to n - 1
.
You're given a list of meetings, where each meeting is represented as [start_time, end_time]
. The time interval is half-closed, meaning [start, end)
includes the start time but excludes the end time. All start times are unique.
The meeting room allocation follows these rules:
-
Lowest available room first: Each meeting gets assigned to the unused room with the smallest number.
-
Delay if no rooms available: If all rooms are busy, the meeting waits until a room becomes free. When it finally starts, it keeps its original duration. For example, if a meeting was supposed to run from time 2 to 5 (duration 3), but all rooms are busy until time 7, it will run from time 7 to 10 (still duration 3).
-
Earlier meetings get priority: When a room becomes available and multiple meetings are waiting, the meeting with the earlier original start time gets the room first.
Your task is to find which room hosted the most meetings. If multiple rooms hosted the same maximum number of meetings, return the room with the smallest number.
For example, if you have 2 rooms and meetings [[0,10], [1,5], [2,7], [3,4]]
:
- Meeting
[0,10]
gets room 0 (runs 0-10) - Meeting
[1,5]
gets room 1 (runs 1-5) - Meeting
[2,7]
waits until room 1 is free at time 5, then runs 5-10 in room 1 - Meeting
[3,4]
waits until a room is free at time 10, then runs 10-11 in room 0
Room 0 hosted 2 meetings, room 1 hosted 2 meetings, so we return room 0 (smaller number).
Intuition
The key insight is that we need to simulate the meeting room allocation process while tracking which rooms are busy and which are idle at any given time.
Think about what information we need at each moment:
- Which rooms are currently free?
- Which rooms are occupied and when will they become free?
- When multiple rooms are free, which one should we pick? (the one with the smallest index)
- When no rooms are free, which meeting ends first?
This naturally leads us to use two data structures:
- A min-heap for idle rooms - sorted by room index, so we can quickly get the smallest available room number
- A min-heap for busy rooms - sorted by end time (and room index as tiebreaker), so we can quickly identify which room becomes free first
The simulation process becomes straightforward:
- Process meetings in chronological order (sort by start time first)
- Before assigning each meeting, check if any busy rooms have become free (their end time ≤ current meeting's start time) and move them to the idle heap
- If we have idle rooms, assign the smallest one
- If no idle rooms exist, we must wait - find the room that becomes free earliest, calculate the delay, and schedule the meeting with the adjusted time
The clever part is handling delays: when a meeting is delayed from time s
to time a
(where a
is when a room becomes available), the meeting duration remains e - s
, so the new end time becomes a + (e - s)
.
By maintaining a counter array cnt[i]
that tracks how many meetings each room hosted, we can easily find our answer at the end by scanning for the maximum count (with the smallest index as tiebreaker).
Learn more about Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation uses two min-heaps as priority queues to manage room allocation efficiently:
Data Structures:
busy
: A min-heap storing(end_time, room_index)
pairs for occupied rooms, sorted by end time firstidle
: A min-heap storing available room indices, sorted by room numbercnt
: An array to track the number of meetings held in each room
Algorithm Steps:
-
Initialize and Sort:
- Sort meetings by start time using
meetings.sort()
- Initialize
idle
heap with all room indices[0, 1, 2, ..., n-1]
- Create empty
busy
heap and counter arraycnt
- Sort meetings by start time using
-
Process Each Meeting: For each meeting
[s, e]
in sorted order:a. Free up rooms: Check if any busy rooms have finished (end time ≤ current start time
s
):while busy and busy[0][0] <= s: heappush(idle, heappop(busy)[1])
This moves completed rooms from
busy
back toidle
.b. Assign room:
-
If idle rooms exist: Take the smallest room index:
i = heappop(idle) cnt[i] += 1 heappush(busy, (e, i))
The room becomes busy until time
e
. -
If no idle rooms: Find the room that becomes free earliest:
a, i = heappop(busy) # a = earliest end time, i = room index cnt[i] += 1 heappush(busy, (a + e - s, i))
The meeting is delayed from time
s
to timea
. Since the duration ise - s
, the new end time becomesa + (e - s)
.
-
-
Find the Answer: Scan through the counter array to find the room with maximum meetings (choosing the smallest index for ties):
ans = 0 for i, v in enumerate(cnt): if cnt[ans] < v: ans = i
Time Complexity: O(m × log m)
where m
is the number of meetings
- Sorting meetings:
O(m × log m)
- Each meeting involves at most
O(log n)
heap operations - Since
n ≤ m
, the overall complexity isO(m × log m)
Space Complexity: O(n)
for the heaps and counter array
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 a concrete example with 3 rooms and meetings [[0,4], [1,3], [2,5], [3,4], [4,6]]
.
Initial Setup:
idle
heap:[0, 1, 2]
(all rooms available)busy
heap:[]
(no rooms occupied)cnt
:[0, 0, 0]
(meeting count for each room)
Processing Meeting [0,4]:
- No busy rooms to free up
- Idle rooms available, take room 0 (smallest)
idle
:[1, 2]
,busy
:[(4, 0)]
,cnt
:[1, 0, 0]
- Room 0 is busy until time 4
Processing Meeting [1,3]:
- Check busy rooms: room 0's end time (4) > current start (1), so no rooms freed
- Idle rooms available, take room 1
idle
:[2]
,busy
:[(3, 1), (4, 0)]
,cnt
:[1, 1, 0]
- Room 1 is busy until time 3
Processing Meeting [2,5]:
- Check busy rooms: earliest end time (3) > current start (2), so no rooms freed
- Idle rooms available, take room 2
idle
:[]
,busy
:[(3, 1), (4, 0), (5, 2)]
,cnt
:[1, 1, 1]
- All rooms now occupied
Processing Meeting [3,4]:
- Check busy rooms: room 1 ends at time 3 ≤ current start (3), so free it
- Move room 1 from busy to idle:
idle
:[1]
,busy
:[(4, 0), (5, 2)]
- Take room 1 from idle
idle
:[]
,busy
:[(4, 0), (4, 1), (5, 2)]
,cnt
:[1, 2, 1]
- Room 1 is busy until time 4
Processing Meeting [4,6]:
- Check busy rooms: both room 0 and 1 end at time 4 ≤ current start (4)
- Free both:
idle
:[0, 1]
,busy
:[(5, 2)]
- Take room 0 (smallest available)
idle
:[1]
,busy
:[(5, 2), (6, 0)]
,cnt
:[2, 2, 1]
Final Result:
cnt
:[2, 2, 1]
(room 0: 2 meetings, room 1: 2 meetings, room 2: 1 meeting)- Rooms 0 and 1 both hosted 2 meetings (maximum)
- Return room 0 (smaller index)
This example demonstrates how the algorithm efficiently manages room allocation using heaps, handles rooms becoming free, and tracks meeting counts to find the answer.
Solution Implementation
1class Solution:
2 def mostBooked(self, n: int, meetings: List[List[int]]) -> int:
3 # Sort meetings by start time
4 meetings.sort()
5
6 # Min heap to track busy rooms: (end_time, room_number)
7 busy_rooms = []
8
9 # Min heap to track available rooms by room number
10 available_rooms = list(range(n))
11 heapify(available_rooms)
12
13 # Counter for number of meetings held in each room
14 meeting_count = [0] * n
15
16 # Process each meeting
17 for start_time, end_time in meetings:
18 # Free up rooms that have finished their meetings
19 while busy_rooms and busy_rooms[0][0] <= start_time:
20 freed_room = heappop(busy_rooms)[1]
21 heappush(available_rooms, freed_room)
22
23 # If there's an available room, use it
24 if available_rooms:
25 room_id = heappop(available_rooms)
26 meeting_count[room_id] += 1
27 heappush(busy_rooms, (end_time, room_id))
28 else:
29 # No available rooms, wait for the earliest room to finish
30 earliest_end_time, room_id = heappop(busy_rooms)
31 meeting_count[room_id] += 1
32 # Schedule meeting right after current meeting ends
33 # Duration is (end_time - start_time)
34 new_end_time = earliest_end_time + (end_time - start_time)
35 heappush(busy_rooms, (new_end_time, room_id))
36
37 # Find the room with the most meetings (smallest index if tie)
38 most_used_room = 0
39 for room_id, count in enumerate(meeting_count):
40 if meeting_count[most_used_room] < count:
41 most_used_room = room_id
42
43 return most_used_room
44
1class Solution {
2 public int mostBooked(int n, int[][] meetings) {
3 // Sort meetings by start time
4 Arrays.sort(meetings, (a, b) -> a[0] - b[0]);
5
6 // Priority queue to track busy rooms: [endTime, roomNumber]
7 // Sorted by end time first, then by room number if end times are equal
8 PriorityQueue<int[]> busyRooms = new PriorityQueue<>((a, b) -> {
9 if (a[0] == b[0]) {
10 return a[1] - b[1]; // Sort by room number if end times are equal
11 }
12 return a[0] - b[0]; // Sort by end time
13 });
14
15 // Priority queue to track available rooms (sorted by room number)
16 PriorityQueue<Integer> availableRooms = new PriorityQueue<>();
17
18 // Initialize all rooms as available
19 for (int roomId = 0; roomId < n; roomId++) {
20 availableRooms.offer(roomId);
21 }
22
23 // Track the count of meetings held in each room
24 int[] meetingCount = new int[n];
25
26 // Process each meeting
27 for (int[] meeting : meetings) {
28 int startTime = meeting[0];
29 int endTime = meeting[1];
30
31 // Free up rooms that have finished their meetings before current start time
32 while (!busyRooms.isEmpty() && busyRooms.peek()[0] <= startTime) {
33 int[] finishedRoom = busyRooms.poll();
34 availableRooms.offer(finishedRoom[1]); // Add room back to available pool
35 }
36
37 int assignedRoom;
38
39 if (!availableRooms.isEmpty()) {
40 // If there are available rooms, use the one with smallest number
41 assignedRoom = availableRooms.poll();
42 busyRooms.offer(new int[] {endTime, assignedRoom});
43 } else {
44 // If no rooms available, wait for the earliest room to finish
45 int[] earliestRoom = busyRooms.poll();
46 assignedRoom = earliestRoom[1];
47 int delayedEndTime = earliestRoom[0] + (endTime - startTime);
48 busyRooms.offer(new int[] {delayedEndTime, assignedRoom});
49 }
50
51 // Increment the meeting count for the assigned room
52 meetingCount[assignedRoom]++;
53 }
54
55 // Find the room with the most meetings (smallest room number if tied)
56 int mostUsedRoom = 0;
57 for (int roomId = 0; roomId < n; roomId++) {
58 if (meetingCount[roomId] > meetingCount[mostUsedRoom]) {
59 mostUsedRoom = roomId;
60 }
61 }
62
63 return mostUsedRoom;
64 }
65}
66
1using ll = long long;
2using pii = pair<ll, int>;
3
4class Solution {
5public:
6 int mostBooked(int n, vector<vector<int>>& meetings) {
7 // Min heap to track available rooms (sorted by room number)
8 priority_queue<int, vector<int>, greater<int>> availableRooms;
9
10 // Min heap to track busy rooms (sorted by end time, then room number)
11 // Each pair contains: (end_time, room_number)
12 priority_queue<pii, vector<pii>, greater<pii>> occupiedRooms;
13
14 // Initialize all rooms as available
15 for (int i = 0; i < n; ++i) {
16 availableRooms.push(i);
17 }
18
19 // Counter for number of meetings held in each room
20 vector<int> meetingCount(n);
21
22 // Sort meetings by start time
23 sort(meetings.begin(), meetings.end());
24
25 // Process each meeting
26 for (auto& meeting : meetings) {
27 int startTime = meeting[0];
28 int endTime = meeting[1];
29
30 // Free up rooms that have finished before current meeting starts
31 while (!occupiedRooms.empty() && occupiedRooms.top().first <= startTime) {
32 availableRooms.push(occupiedRooms.top().second);
33 occupiedRooms.pop();
34 }
35
36 int assignedRoom = 0;
37
38 // Case 1: There's an available room
39 if (!availableRooms.empty()) {
40 // Assign the lowest numbered available room
41 assignedRoom = availableRooms.top();
42 availableRooms.pop();
43 occupiedRooms.push({endTime, assignedRoom});
44 }
45 // Case 2: All rooms are occupied, need to wait
46 else {
47 // Get the room that will be free earliest
48 auto earliestFreeRoom = occupiedRooms.top();
49 occupiedRooms.pop();
50
51 assignedRoom = earliestFreeRoom.second;
52 ll delayedEndTime = earliestFreeRoom.first + (endTime - startTime);
53 occupiedRooms.push({delayedEndTime, assignedRoom});
54 }
55
56 // Increment meeting count for the assigned room
57 ++meetingCount[assignedRoom];
58 }
59
60 // Find the room with most meetings (lowest numbered room in case of tie)
61 int mostUsedRoom = 0;
62 for (int i = 0; i < n; ++i) {
63 if (meetingCount[mostUsedRoom] < meetingCount[i]) {
64 mostUsedRoom = i;
65 }
66 }
67
68 return mostUsedRoom;
69 }
70};
71
1// Type aliases for readability
2type MeetingTime = [number, number]; // [endTime, roomNumber]
3
4// Global function to find the most booked meeting room
5function mostBooked(n: number, meetings: number[][]): number {
6 // Min heap to track available rooms (sorted by room number)
7 const availableRooms: number[] = [];
8
9 // Min heap to track busy rooms (sorted by end time, then room number)
10 // Each tuple contains: [endTime, roomNumber]
11 const occupiedRooms: MeetingTime[] = [];
12
13 // Initialize all rooms as available (0 to n-1)
14 for (let i = 0; i < n; i++) {
15 availableRooms.push(i);
16 }
17 // Sort available rooms to maintain min heap property
18 availableRooms.sort((a, b) => a - b);
19
20 // Counter for number of meetings held in each room
21 const meetingCount: number[] = new Array(n).fill(0);
22
23 // Sort meetings by start time
24 meetings.sort((a, b) => a[0] - b[0]);
25
26 // Process each meeting in chronological order
27 for (const meeting of meetings) {
28 const startTime = meeting[0];
29 const endTime = meeting[1];
30
31 // Free up rooms that have finished before current meeting starts
32 while (occupiedRooms.length > 0 && occupiedRooms[0][0] <= startTime) {
33 // Remove the room with earliest end time
34 const freedRoom = occupiedRooms.shift()!;
35 // Add the freed room back to available rooms
36 availableRooms.push(freedRoom[1]);
37 // Maintain min heap property for available rooms
38 availableRooms.sort((a, b) => a - b);
39 }
40
41 let assignedRoom = 0;
42
43 // Case 1: There's an available room
44 if (availableRooms.length > 0) {
45 // Assign the lowest numbered available room
46 assignedRoom = availableRooms.shift()!;
47 // Add room to occupied list with its end time
48 occupiedRooms.push([endTime, assignedRoom]);
49 // Maintain min heap property for occupied rooms
50 occupiedRooms.sort((a, b) => {
51 if (a[0] !== b[0]) return a[0] - b[0]; // Sort by end time first
52 return a[1] - b[1]; // Then by room number
53 });
54 }
55 // Case 2: All rooms are occupied, need to wait for earliest available
56 else {
57 // Get the room that will be free earliest
58 const earliestFreeRoom = occupiedRooms.shift()!;
59
60 assignedRoom = earliestFreeRoom[1];
61 // Calculate delayed end time (meeting duration remains the same)
62 const delayedEndTime = earliestFreeRoom[0] + (endTime - startTime);
63 // Add room back to occupied list with new end time
64 occupiedRooms.push([delayedEndTime, assignedRoom]);
65 // Maintain min heap property for occupied rooms
66 occupiedRooms.sort((a, b) => {
67 if (a[0] !== b[0]) return a[0] - b[0]; // Sort by end time first
68 return a[1] - b[1]; // Then by room number
69 });
70 }
71
72 // Increment meeting count for the assigned room
73 meetingCount[assignedRoom]++;
74 }
75
76 // Find the room with most meetings (lowest numbered room in case of tie)
77 let mostUsedRoom = 0;
78 for (let i = 0; i < n; i++) {
79 if (meetingCount[mostUsedRoom] < meetingCount[i]) {
80 mostUsedRoom = i;
81 }
82 }
83
84 return mostUsedRoom;
85}
86
Time and Space Complexity
Time Complexity: O(m * log m + m * log n)
where m
is the number of meetings and n
is the number of rooms.
- Sorting meetings takes
O(m * log m)
- For each meeting, we perform:
- While loop to free rooms: In the worst case, all rooms might become free, leading to
O(n * log n)
operations across all meetings (amortizedO(log n)
per meeting) - Push/pop operations on heaps: Each heap operation takes
O(log n)
time - Each meeting is processed exactly once with at most constant heap operations
- While loop to free rooms: In the worst case, all rooms might become free, leading to
- Finding the maximum count at the end takes
O(n)
- Overall:
O(m * log m + m * log n + n)
=O(m * log m + m * log n)
since typicallym ≥ n
Space Complexity: O(n)
busy
heap can contain at mostn
rooms:O(n)
idle
heap contains at mostn
rooms:O(n)
cnt
array of sizen
:O(n)
- The sorting operation might use
O(log m)
space for the sorting algorithm's recursion stack (if using quicksort/mergesort) - Overall:
O(n + log m)
=O(n)
asn
is typically the dominant factor
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Duration Calculation When Delaying Meetings
The Pitfall: When all rooms are busy and a meeting needs to be delayed, a common mistake is to incorrectly calculate the new end time. Developers often make one of these errors:
- Setting the new end time as just
earliest_end_time + end_time
(adding the original end time instead of duration) - Setting it as
earliest_end_time + 1
(assuming all meetings have duration 1) - Using the original
end_time
without adjustment
Wrong Implementation:
# INCORRECT - adds end_time instead of duration new_end_time = earliest_end_time + end_time # INCORRECT - uses fixed duration new_end_time = earliest_end_time + 1 # INCORRECT - doesn't account for delay heappush(busy_rooms, (end_time, room_id))
Correct Implementation:
# CORRECT - adds the actual duration (end_time - start_time) new_end_time = earliest_end_time + (end_time - start_time) heappush(busy_rooms, (new_end_time, room_id))
2. Not Freeing All Available Rooms
The Pitfall: Using if
instead of while
when freeing up rooms, which only frees one room even when multiple rooms have finished.
Wrong Implementation:
# INCORRECT - only frees one room if busy_rooms and busy_rooms[0][0] <= start_time: freed_room = heappop(busy_rooms)[1] heappush(available_rooms, freed_room)
Correct Implementation:
# CORRECT - frees all rooms that have finished while busy_rooms and busy_rooms[0][0] <= start_time: freed_room = heappop(busy_rooms)[1] heappush(available_rooms, freed_room)
3. Forgetting to Sort Meetings
The Pitfall: Processing meetings in the given order instead of chronological order by start time. The problem states that meetings should be assigned based on their start times.
Wrong Implementation:
# INCORRECT - processes meetings in given order for start_time, end_time in meetings: # process meeting
Correct Implementation:
# CORRECT - sorts meetings first meetings.sort() for start_time, end_time in meetings: # process meeting
4. Using Wrong Heap for Available Rooms
The Pitfall: Using a regular list or not properly maintaining the min-heap property for available rooms, leading to incorrect room allocation (not choosing the lowest numbered available room).
Wrong Implementation:
# INCORRECT - doesn't maintain sorted order
available_rooms = list(range(n))
# Later: available_rooms.append(freed_room) instead of heappush
Correct Implementation:
# CORRECT - uses heap operations
available_rooms = list(range(n))
heapify(available_rooms)
# Later: heappush(available_rooms, freed_room)
Which technique can we use to find the middle of a linked list?
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
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
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
Want a Structured Path to Master System Design Too? Don’t Miss This!