Problem overview
The function meeting_rooms receives a vector of meetings, each a two-element vector [start, end], and returns the smallest number of rooms that lets every meeting happen. A meeting that ends at the moment another starts doesn't overlap it, so [1, 2] and [2, 3] can share a room. Each end is larger than its start.
For [[5, 10], [0, 30], [15, 20]] the answer is 2. The meetings [5, 10] and [15, 20] can use one room, one after the other, while [0, 30] runs the whole time and needs its own.
Approach: sweep through start and end events
The number of rooms needed equals the largest number of meetings that are running at the same moment. Finding that peak by comparing every pair of meetings is slow. A cleaner way is to look at the timeline instead of at the meetings.
Turn each meeting into two events: a start at its start time that adds one running meeting, and an end at its end time that removes one. Sort all events by time and sweep through them, keeping a counter of meetings in progress. After every event, compare the counter with the best value so far. The best value is the answer.
One detail decides correctness: what to do with events at the same time. When one meeting ends exactly as another begins, the room is free in time for the new one. So at equal times, end events must come before start events. Giving an end the value -1 and a start the value 1, sorting by time and then by that value does exactly this, and the same value can be added to the counter.
On the example the events are (0, start), (5, start), (10, end), (15, start), (20, end), (30, end). The counter goes 1, 2, 1, 2, 1, 0. Its peak is 2.
The sort makes the time O(n log n) and the event list needs O(n) space, matching the statement's limits.
Step by step
- Create the event list.
eventsis a mutableVec<(i32, i32)>made withVec::new(). Each event is a tuple of a time and a type. The annotation tells Rust what the empty vector will hold. - Loop over the meetings with
for interval in &intervals. Iterating over a reference borrows the vector rather than moving it. - Push the start event.
(interval[0], 1): the start time, tagged 1. - Push the end event.
(interval[1], -1): the end time, tagged -1. - Sort the events with
sort_byand a closure. If the times are equal, compare the types, so -1 comes before 1. Otherwise compare the times. - Declare the counters.
answerholds the largest count so far andno_of_meetingsthe current count, bothi32and both starting at 0. - Sweep. Loop over
&eventswith the pattern(_, event_type), ignoring the time. - Update the count. Add
event_typetono_of_meetings. That is why the types are 1 and -1. - Track the peak with
answer = answer.max(no_of_meetings). - Return
answer.
The solution in Rust
impl Solution {
fn meeting_rooms(intervals: Vec<Vec<i32>>) -> i32 {
let mut events: Vec<(i32, i32)> = Vec::new();
for interval in &intervals {
events.push((interval[0], 1));
events.push((interval[1], -1));
}
events.sort_by(|a, b| {
if a.0 == b.0 {
a.1.cmp(&b.1)
} else {
a.0.cmp(&b.0)
}
});
let mut answer = 0i32;
let mut no_of_meetings = 0i32;
for (_, event_type) in &events {
no_of_meetings += event_type;
answer = answer.max(no_of_meetings);
}
answer
}
}
The Rust details worth knowing:
- Tuples.
(i32, i32)groups two values, read by position as.0and.1. The first is the time and the second the type. - Borrowing in
forloops.for interval in &intervalsyields&Vec<i32>references and leavesintervalsusable afterwards. Without the&the loop would consume the vector. Inside,interval[0]copies ani32out of it. - The comparator.
sort_bytakes a closure receiving two references and returning anOrdering:Less,EqualorGreater.cmptakes a reference to the other value, hence&b.1. Both branches of theifproduce anOrdering, and since neither ends with a semicolon, the chosen one is the closure's value. - Destructuring a reference. Looping over
&eventsgives&(i32, i32). The pattern(_, event_type)matches through the reference, ignores the time with_, and bindsevent_typeas a&i32. +=with a reference. Rust implementsi32 += &i32, sono_of_meetings += event_typecompiles without writing*event_type.- Typed literals.
0i32fixes the type with no separate annotation.
Complexity
Time: O(n log n). Building the events is O(n). Sorting 2n events costs O(n log n), and the sweep is O(n).
Space: O(n). The list holds two tuples per meeting.
Edge cases and common mistakes
- Back-to-back meetings.
[[1, 2], [2, 3]]needs 1 room. At time 2 the end (-1) sorts before the start (1), so the count drops to 0 before rising again. If starts came first, the count would reach 2. - Everything overlapping.
[[0, 5], [1, 6], [2, 7]]needs 3 rooms. - A single meeting. The answer is 1.
- No meetings. An empty vector gives no events, and the function returns 0.
- Sorting by time only. A comparator that ignores the type leaves equal times in whatever order the events were pushed. For
[[2, 3], [1, 2]]the start at time 2 is pushed before the end at time 2, so the count briefly reaches 2 and the answer is wrong. The tie-break on the type is what prevents it. - Taking the last count instead of the peak. The counter ends at 0, so the answer has to be the maximum seen along the way.
Testing
[[5,10],[0,30],[15,20]]gives2.[[1,2],[2,3]]gives1.[[0,5],[1,6],[2,7]]gives3.[[3,4]]gives1.[]gives0.
