Facebook Pixel

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:

  1. Lowest available room first: Each meeting gets assigned to the unused room with the smallest number.

  2. 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).

  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).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. A min-heap for idle rooms - sorted by room index, so we can quickly get the smallest available room number
  2. 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 first
  • idle: A min-heap storing available room indices, sorted by room number
  • cnt: An array to track the number of meetings held in each room

Algorithm Steps:

  1. 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 array cnt
  2. 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 to idle.

    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 time a. Since the duration is e - s, the new end time becomes a + (e - s).

  3. 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 is O(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 Evaluator

Example 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 (amortized O(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
  • 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 typically m ≥ n

Space Complexity: O(n)

  • busy heap can contain at most n rooms: O(n)
  • idle heap contains at most n rooms: O(n)
  • cnt array of size n: 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) as n 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)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More