Problem overview
The method meetingRooms(intervals) receives meetings as [start, end] pairs and returns the smallest number of rooms that can hold all of them, so that no two meetings in the same room overlap. A meeting that ends at the exact moment another starts doesn't overlap it: [1, 2] and [2, 3] can share a room.
For [[5, 10], [0, 30], [15, 20]] the answer is 2. The meeting [0, 30] lasts the whole time, so it needs its own room. The other two don't overlap each other, so they can share the second room. The statement asks for O(n log n) time and O(n) extra space.
Approach: count the meetings in progress
The number of rooms you need equals the largest number of meetings happening at the same moment. Checking every moment is not possible, but the count only changes when a meeting starts or ends, so only those moments matter.
Turn each meeting into two events: a start tagged 1 and an end tagged -1. Sort the events by time and sweep through them, keeping a running total. A start adds 1 to the number of meetings in progress, an end subtracts 1, and the largest total seen is the answer.
Trace of the example. The events sorted by time are: 0 start, 5 start, 10 end, 15 start, 20 end, 30 end. The running total goes 1, 2, 1, 2, 1, 0. Its maximum is 2.
When two events share the same time, the order decides the answer. If a meeting ends at 2 and another starts at 2, the end must be handled first, so that the room is freed before it is needed again. Sorting ties by the tag puts -1 ahead of 1. With [[1, 2], [2, 3]], the events at time 2 are [2, -1] and [2, 1]; handled in that order, the total never goes above 1. In the opposite order it would reach 2, which wrongly asks for a second room.
Step by step
- Create the event list. Declare
eventsas an emptynumber[][]. Each event is a pair of a time and a tag. - Loop over the meetings with
for...ofand destructure each intostartandend. - Push the start event
[start, 1]. - Push the end event
[end, -1], as one pair. - Sort the events with a comparator. When the times are equal, order by the tag with
a[1] - b[1], so ends come first. Otherwise order by time witha[0] - b[0]. - Set up the counters.
noOfMeetingscounts meetings in progress andanswerkeeps the maximum, both starting at 0. - Loop over the sorted events and destructure each into
timeandtype. - Add the tag to
noOfMeetings. The values 1 and -1 turn the start or end decision into a single addition. - Track the maximum with
Math.max(answer, noOfMeetings). - Return
answerafter the loop, once every event has been processed.
The solution in TypeScript
class Solution {
meetingRooms(intervals: number[][]): number {
const events: number[][] = [];
for (const [start, end] of intervals) {
events.push([start, 1]);
events.push([end, -1]);
}
events.sort((a, b) => {
if (a[0] === b[0]) return a[1] - b[1];
return a[0] - b[0];
});
let answer = 0, noOfMeetings = 0;
for (const [time, type] of events) {
noOfMeetings += type;
answer = Math.max(answer, noOfMeetings);
}
return answer;
}
}
TypeScript details worth knowing:
- Annotate the empty array. An empty array literal has no elements to infer from, so
const events: number[][] = []tells the compiler what it will hold.constonly fixes the variable, andpushstill works. - Destructuring in
for...of.for (const [start, end] of intervals)unpacks each pair into two names. Each iteration creates new bindings, which is whyconstis allowed in the loop header. In the second loop,timeis unpacked but not used; the sort has already placed events in time order. - The comparator is required. Without one,
sortconverts each element to a string and compares text. For these events,sort()puts[5, 1]after[30, -1], since"5,1"comes after"30,-1"alphabetically. The sweep would then see events out of time order. - Ties in the comparator.
===compares the times without type conversion. Sorting by time alone would leave tied events in their original order, which is the order they were pushed in, so a start could come before an end at the same time. - One
letfor two counters.let answer = 0, noOfMeetings = 0declares both, and TypeScript infersnumber. - Types are erased. Annotations such as
number[][]are checked while compiling and gone from the JavaScript that runs.
Complexity
Time: O(n log n). There are 2n events. Sorting them costs O(n log n) and the sweep costs O(n).
Space: O(n). The event list holds two pairs per meeting. The input array isn't modified.
Edge cases and common mistakes
- No meetings. An empty list produces no events and the answer is 0.
- Touching meetings.
[[1, 2], [2, 3]]needs 1 room. Reversing the tie order, so that starts come first, would report 2. - Identical meetings. Three copies of
[1, 5]need 3 rooms. - Nested meetings.
[[0, 10], [2, 3], [4, 5], [6, 7]]needs 2 rooms: the long one, and one shared by the short ones. - Unordered input.
[[7, 10], [2, 4]]needs 1 room. The sort handles any order of the input. - Forgetting the comparator. The default text ordering gives wrong event order, and so a wrong maximum.
- Returning inside the loop. The maximum is known only after every event has been seen.
Testing
[[5, 10], [0, 30], [15, 20]]gives2.[[1, 2], [2, 3]]gives1.[[1, 3], [2, 4]]gives2.[[1, 5], [1, 5], [1, 5]]gives3.[]gives0.