Problem overview
The method meetingRooms receives intervals, an array of [start, end] pairs, one per meeting. It returns the smallest number of rooms that lets every meeting happen without two meetings sharing a room at the same moment. Each end is greater than its start. Two meetings that merely touch, such as [1, 2] and [2, 3], don't overlap and can use the same room.
Take [[5, 10], [0, 30], [15, 20]]. The meeting [0, 30] runs the whole time, and the other two never overlap each other. One room holds [5, 10] and then [15, 20], a second room holds [0, 30], so the answer is 2.
The statement asks for O(n log n) time and O(n) extra space.
Approach: count the meetings running at once
The number of rooms needed equals the largest number of meetings in progress at any single moment. Comparing every pair of meetings to find that would take O(n²) work. A faster way is to stop thinking about meetings and think about events instead.
Every meeting produces two events: a start, which adds one running meeting, and an end, which removes one. Put all the events in time order and walk through them while keeping a running count. The highest value the count ever reaches is the answer.
Sorting costs O(n log n) and the walk is a single pass, so the total fits the budget. The events array holds 2n pairs, which is O(n) space.
When a start and an end share the same time, the order matters. If a meeting ends at 10 and another starts at 10, the first one should be processed first. Its room is free by the time the second one needs it, and the count never rises to 2 for them. Tagging starts with 1 and ends with -1 gives the rule for free: sorting by time and then by tag puts -1 before 1.
Step by step
- Create an empty list of events. It is a
varof type[[Int]]because the loop appends to it, and each event is a small[time, type]array. - Loop over
intervals. Each element is one meeting. - Append the start event
[interval[0], 1]. The tag1means one more meeting is running. - Append the end event
[interval[1], -1]. The tag-1means one fewer. - Sort the events in place by time, breaking ties so that
-1comes before1. An array ofIntarrays has no built-in ordering in Swift, so the sort needs a closure that says when the first event comes first. - Declare the two counters,
answerandnoOfMeetings, both starting at 0. The first holds the best value seen so far, the second the number of meetings running right now. - Loop over the sorted events.
- Add the event's tag to the running count. Because the tags are
1and-1, one addition handles both kinds of event. - Keep the maximum. After each update,
answerbecomes the larger of itself and the current count. - Return
answerafter the loop. The peak is known only after every event has been processed.
The solution in Swift
class Solution {
func meetingRooms(_ intervals: [[Int]]) -> Int {
var events = [[Int]]()
for interval in intervals {
events.append([interval[0], 1])
events.append([interval[1], -1])
}
events.sort { a, b in
if a[0] == b[0] { return a[1] < b[1] }
return a[0] < b[0]
}
var answer = 0, noOfMeetings = 0
for event in events {
noOfMeetings += event[1]
answer = max(answer, noOfMeetings)
}
return answer
}
}
The Swift details worth a closer look:
- The sort closure.
sortwith a trailing closure takes two elements and returnstruewhen the first must come before the second. Hereaandbare both[Int]events. The closure has several statements, so Swift needs the explicitreturnkeywords. sortversussorted.sortreorderseventsin place, which is why it must be avar.sortedwould return a new array and leave the original untouched.[[Int]](). This calls the initializer of an array of integer arrays and gives an empty one. Writingvar events: [[Int]] = []means the same.- One
var, two names.var answer = 0, noOfMeetings = 0declares both variables in a single statement. - Global
max. Swift has a free functionmax(_:_:)that returns the larger of two comparable values. - Value semantics. The
intervalsparameter is never modified. The events are new arrays, so the caller's data stays as it was.
Complexity
Time: O(n log n). Building the events takes O(n), sorting 2n events takes O(n log n), and the final loop is O(n). The sort dominates.
Space: O(n). The events array stores two small arrays per meeting. The sort works in place on that array, and the counters take constant space.
Edge cases and common mistakes
- Touching meetings.
[[1, 2], [2, 3]]returns 1. At time 2 the end (-1) sorts before the start (1), so the count goes 1, 0, 1, 0 and never reaches 2. - No meetings. An empty array gives no events, the second loop never runs, and the method returns 0.
- Everything overlaps.
[[1, 5], [2, 6], [3, 7]]returns 3. - Ties sorted the other way. If starts came before ends at equal times,
[[1, 2], [2, 3]]would report 2 rooms. The tie-break line is what makes the touching rule work. - Sorting only by time. The order of equal-time events would then be arbitrary, and the answer could change for the worse.
- Unsorted input. Meetings can arrive in any order.
[[7, 10], [2, 4]]returns 1. The sort is what makes the running count meaningful. - Returning inside the loop. The peak is only known once every event has been seen.
Testing
[[5, 10], [0, 30], [15, 20]]gives2.[[1, 2], [2, 3]]gives1.[[1, 5], [2, 6], [3, 7]]gives3.[[0, 5], [5, 10], [5, 8]]gives2.[]gives0.
