Problem overview
The method meetingRooms receives intervals, an Array[Array[Int]] in which each inner array holds the start and end time of one meeting. It returns the smallest number of rooms that lets every meeting happen without anyone waiting. Each end time is greater than its start time, and meetings that only touch, such as [1, 2] and [2, 3], don't overlap.
For [[5, 10], [0, 30], [15, 20]] the answer is 2. The meeting from 0 to 30 is in progress the whole time, so it needs its own room. The other two don't overlap each other, so they can share a second room.
The statement asks for O(n log n) time and O(n) extra space, where n is the number of meetings.
Approach: turn meetings into events and sweep through time
The number of rooms you need equals the largest number of meetings running at the same moment. Comparing every pair of meetings to find that would be quadratic. A sweep over time is faster.
Split every meeting into two events: a start, which adds one running meeting, and an end, which removes one. Sort the events by time and walk through them while keeping a counter of running meetings. The highest value the counter ever reaches is the answer. Sorting costs O(n log n), the walk costs O(n), and the list of events holds 2n entries, which is O(n) space.
Each event is a pair (time, type), with type 1 for a start and -1 for an end. Those two numbers are chosen so that updating the counter is a single addition: noOfMeetings += event(1).
Ties need care. If one meeting ends at time 10 and another starts at 10, the room is free in time for the new meeting, so the end must be processed before the start. Sorting by time and then by type puts -1 before 1, which does exactly that.
Step by step
- Create the event list. A
valholding an emptyscala.collection.mutable.ArrayBuffer[Array[Int]]. Scala's default collections are immutable, so you need the mutable buffer to append. Thevalonly fixes which buffereventsrefers to; the buffer itself can still grow. - Loop over the meetings with
for (interval <- intervals). - Add the start event
Array(interval(0), 1)with+=. Scala indexes arrays with parentheses, sointerval(0)is the start time. - Add the end event
Array(interval(1), -1)the same way. - Sort the events with
sortWith. Its function receives two events and returnstruewhen the first must come before the second: compare the types when the times are equal, and the times otherwise.sortWithreturns a new buffer instead of sorting in place, so the result goes intosorted. - Declare the counters. Two
vars,answerandnoOfMeetings, both starting at 0. They change inside the next loop, sovalwould not work. - Loop over
sorted, not overevents, which is still in insertion order. - Update the running count by adding the event's type.
- Keep the maximum with
math.max(answer, noOfMeetings). - End with
answeras the last expression. A Scala method returns the value of its last expression, so noreturnis needed.
The solution in Scala
class Solution {
def meetingRooms(intervals: Array[Array[Int]]): Int = {
val events = scala.collection.mutable.ArrayBuffer[Array[Int]]()
for (interval <- intervals) {
events += Array(interval(0), 1)
events += Array(interval(1), -1)
}
val sorted = events.sortWith((a, b) => {
if (a(0) == b(0)) a(1) < b(1)
else a(0) < b(0)
})
var answer = 0
var noOfMeetings = 0
for (event <- sorted) {
noOfMeetings += event(1)
answer = math.max(answer, noOfMeetings)
}
answer
}
}
The Scala details worth a closer look:
ifis an expression. The body of the comparison function is anif/elsewhose value is aBoolean, and that value is what the function returns. There is noreturnand no ternary operator.vars inside aforloop. Afor (x <- xs) { ... }loop runs its body as a function for each element. Scala still lets that body assign tovars declared outside it, soanswerandnoOfMeetingswork as you would expect.sortWithis stable and not in place. The originaleventsbuffer keeps its order. With the buffer(3, 1), (1, 1), the sorted copy is(1, 1), (3, 1), andeventsstill lists(3, 1)first.- Arrays as pairs. A two-element
Array[Int]works as a pair here, but the compiler doesn't know it has two elements. Readingevent(2)would compile and fail at run time.
Complexity
Time: O(n log n). Building the events takes O(n), sorting the 2n events takes O(n log n), and the sweep takes O(n).
Space: O(n). The buffer holds 2n small arrays, and sortWith creates a second buffer of the same size.
Edge cases and common mistakes
- Touching meetings.
[[1, 2], [2, 3]]returns 1. At time 2 the end event comes first, so the counter drops to 0 before it goes up again. - Sorting by time only. This breaks the tie rule. For
[[2, 3], [1, 2]], a comparison that looks only ata(0) < b(0)keeps the start of the first meeting ahead of the end of the second at time 2, and the count reaches 2 instead of 1. - No meetings. An empty array has no events, the loop never runs, and the method returns 0.
- Identical meetings. Three copies of
[0, 5]return 3, because all three starts are processed before any end. - Iterating over
events. The unsorted buffer lists each meeting's start and end together, so the counter would never go above 1. - Using
valfor the counters. Scala reports an error on the first reassignment.
Testing
[[5, 10], [0, 30], [15, 20]]gives2.[[1, 2], [2, 3]]gives1.[[2, 3], [1, 2]]gives1.[[0, 5], [0, 5], [0, 5]]gives3.[]gives0.
