Start learning free

LESSON 321 OF 338 · GREEDY

Meeting Rooms in Go: O(n log n) Sweep Over Sorted Events

Practice O(n log n) Read the full explanation

Given an array consisting of meeting time intervals, which are represented by the start and end times ([s0, e0], [s1, e1], ...], determine the minimum number of meeting rooms needed in order to be able to accomodate all meetings at every moment of time.


Example:

Input: [[5, 10], [0, 30], [15, 20]]
Output: 2
Explanation: 1st and 3rd meetings can be assigned to the first room
             2nd meeting can be assigned to room 2

 

Note:

  1. You may assume the interval's end point is always greater than its start point.
  2. Intervals like [1,2] and [2,3] have borders "touching" but they don't overlap.

Your algorithm should run in O(n log n) time and use O(n) extra space.


STEP 1 OF 10
    Ctrl + Enter to check

    GO · GREEDY

    Meeting Rooms in Go, explained

    Problem overview

    The method meetingRooms(intervals) receives meetings as a slice of [start, end] pairs and returns the minimum number of rooms needed so that every meeting gets a room for its whole duration. Meetings that touch, where one ends at the moment another begins, don't overlap and can share a room. Each end is greater than its start.

    For [[5, 10], [0, 30], [15, 20]] the answer is 2. The meeting from 0 to 30 runs the whole time and needs its own room. The other two, 5 to 10 and 15 to 20, 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.

    Approach: turn meetings into events and sweep through time

    The number of rooms needed is the largest number of meetings that are in progress at the same moment. Comparing every pair of meetings to find that peak would take O(n²) time. A sweep does it with a sort.

    Split each meeting into two events: a start at time s, which adds a meeting to the count, and an end at time e, which removes one. Sort all events by time and walk through them in order, keeping a running count of meetings in progress. The highest value the count reaches is the answer.

    Ties need care. If one meeting ends at time 10 and another starts at 10, the two events share a time. Processing the end first frees the room before the start asks for one, so the touching meetings share a room, as the statement requires. If the start were processed first, the count would briefly show 2 and the answer would be too high. The events are therefore stored as pairs [time, type], with type 1 for a start and -1 for an end. Comparing the type when the times are equal puts -1 before 1.

    For the example, the sorted events and the running count are:

    [0 1]    count 1
    [5 1]    count 2
    [10 -1]  count 1
    [15 1]   count 2
    [20 -1]  count 1
    [30 -1]  count 0
    

    The largest count is 2, which is the answer.

    Step by step

    1. Create the events slice. Declare events as a [][]int with make, length 0 and capacity len(intervals)*2. Every meeting adds exactly two events, so append never needs to grow the storage.
    2. Loop over the meetings with for _, interval := range intervals. The blank identifier discards the index, which would otherwise be an unused variable.
    3. Append the start event []int{interval[0], 1}. Assign the result of append back to events.
    4. Append the end event []int{interval[1], -1} the same way.
    5. Sort the events with sort.Slice. The comparison function returns true when the event at i must come first: if the times are equal, the smaller type wins; otherwise the earlier time wins.
    6. Prepare the counters. Declare answer and noOfMeetings, both 0, in one statement. The second is the number of meetings in progress, and the first is the largest value seen.
    7. Walk the sorted events with another range loop that discards the index.
    8. Update the count by adding the event's type: noOfMeetings += event[1]. The values 1 and -1 turn "add or remove a meeting" into one addition.
    9. Track the peak with answer = max(answer, noOfMeetings).
    10. Return answer.

    The solution in Go

    type Solution struct {}
    
    func (this Solution) meetingRooms(intervals [][]int) int {
        events := make([][]int, 0, len(intervals)*2)
        for _, interval := range intervals {
            events = append(events, []int{interval[0], 1})
            events = append(events, []int{interval[1], -1})
        }
        sort.Slice(events, func(i, j int) bool {
            if events[i][0] == events[j][0] {
                return events[i][1] < events[j][1]
            }
            return events[i][0] < events[j][0]
        })
        answer, noOfMeetings := 0, 0
        for _, event := range events {
            noOfMeetings += event[1]
            answer = max(answer, noOfMeetings)
        }
        return answer
    }
    

    Go details worth a closer look:

    • make([][]int, 0, n) takes the element type, the starting length and an optional capacity. With length 0 the slice starts empty, and the reserved capacity means the appends fill existing storage instead of reallocating. Without the capacity the code would still be correct, just slower.
    • append returns a slice. It may allocate a new array, so the result must be assigned back. Calling append(events, x) and ignoring the result would leave events unchanged.
    • []int{interval[0], 1} is a slice literal. Each event is its own two-element slice, which is why events has type [][]int.
    • The blank identifier _ is needed because Go rejects unused variables. range over a slice yields an index and an element, and here only the element is wanted.
    • sort.Slice needs the sort package imported and sorts in place. The function receives indices into events, not the events. It isn't a stable sort, but two events with the same time and type are interchangeable, so the order among them doesn't matter.
    • Built-in max is available from Go 1.21 and needs no import.

    Complexity

    There are 2n events. Sorting them takes O(n log n) time and the sweep is linear, so the total is O(n log n). The events slice stores 2n small slices, so the extra space is O(n), as the statement allows. The function does not change intervals.

    Edge cases and common mistakes

    • Touching meetings. For [[1, 2], [2, 3]] the sorted events are [1 1], [2 -1], [2 1], [3 -1], so the count never passes 1 and the answer is 1. Sorting by time alone, with no tie-break, could put the start first and report 2.
    • One meeting. The answer is 1.
    • Fully nested or identical meetings. Three meetings with the same start and end give 3, because their start events all come before their end events.
    • No meetings. The loops do nothing and the answer is 0.
    • Reversing the tie-break. Using > on the types puts starts before ends and overcounts touching meetings.
    • Forgetting to assign append's result, which loses the events.

    Testing

    Try [[5, 10], [0, 30], [15, 20]] (answer 2), [[1, 2], [2, 3]] (answer 1) and [[1, 4], [2, 5], [3, 6]] (answer 3, since all three are running at time 3).

    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free