LeetCode 253. 会议室 II

253. 会议室 II

给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量 。

示例 1:

输入:intervals = [[0,30],[5,10],[15,20]]
输出:2

示例 2:

输入:intervals = [[7,10],[2,4]]
输出:1

提示:

  • 1 <= intervals.length <= 10^4
  • 0 <= starti < endi <= 10^6

提示 1

Think about how we would approach this problem in a very simplistic way. We will allocate rooms to meetings that occur earlier in the day v/s the ones that occur later on, right?


提示 2

If you've figured out that we have to sort the meetings by their start time, the next thing to think about is how do we do the allocation?
There are two scenarios possible here for any meeting. Either there is no meeting room available and a new one has to be allocated, or a meeting room has freed up and this meeting can take place there.


提示 3

An important thing to note is that we don't really care which room gets freed up while allocating a room for the current meeting. As long as a room is free, our job is done.

We already know the rooms we have allocated till now and we also know when are they due to get free because of the end times of the meetings going on in those rooms. We can simply check the room which is due to get vacated the earliest amongst all the allocated rooms.


提示 4

Following up on the previous hint, we can make use of a min-heap to store the end times of the meetings in various rooms.

So, every time we want to check if any room is free or not, simply check the topmost element of the min heap as that would be the room that would get free the earliest out of all the other rooms currently occupied.

If the room we extracted from the top of the min heap isn't free, then no other room is. So, we can save time here and simply allocate a new room.

解法1:差分数组

会议将会在 左开右闭 时间区间 [starti, endi) 举办。

类似题型:LeetCode 252. 会议室-CSDN博客

Java版:

class Solution {
    public int minMeetingRooms(int[][] intervals) {
        int m = intervals.length;
        int mint = Integer.MAX_VALUE;
        int maxt = Integer.MIN_VALUE;
        for (int[] interval: intervals) {
            mint = Math.min(mint, interval[0]);
            maxt = Math.max(maxt, interval[1]);
        }

        int n = maxt - mint + 1;
        int[] diff = new int[n];
        for (int[] interval: intervals) {
            diff[interval[0] - mint]++;
            diff[interval[1] - mint]--;
        }
        
        int ans = diff[0];
        for (int i = 1; i < n; i++) {
            diff[i] += diff[i - 1];
            ans = Math.max(ans, diff[i]);
        }
        return ans;
    }
}

Pyhton3版:

class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        m = len(intervals)
        mint = inf 
        maxt = -inf 

        for start, end in intervals:
            mint = min(mint, start)
            maxt = max(maxt, end)
        
        n = maxt - mint + 1
        diff = [0] * n
        for start, end in intervals:
            diff[start - mint] += 1
            diff[end - mint] -= 1
        
        ans = diff[0]
        for i in range(1, n):
            diff[i] += diff[i - 1]
            ans = max(ans, diff[i])
        return ans

复杂度分析

  • 时间复杂度:O(m + n),其中 m 是数组 intervals 的长度,n 是差分数组 diff 的长度。
  • 空间复杂度:O(n),n 是差分数组 diff 的长度。

解法2:优先队列 实现最小堆

我们不能在任意顺序处理给定的会议。处理会议的最基本方法是以其“开始时间”的增加顺序处理,这是我们将遵循的顺序。毕竟,安排一个在早上 9 点举行的会议在下午 5 点的会议之前是有道理的,对吗?
让我们以样本会议时间来做一个例题的演练,看看我们的算法应该能够有效地做什么。
我们将考虑以下会议时间作为我们的例子 (1,10),(2,7),(3,19),(8,12),(10,20),(11,30)。元组的第一部分是会议的开始时间,第二个值表示结束时间。我们按照开始时间的排序顺序来考虑会议。第一个图显示了前三个会议,它们需要单独的房间来进行,因为它们的时间会相互冲突。

接下来的三次会议开始占用一些已有的房间。然而,最后一个需要一个全新的房间,总的来说我们有四个不同的房间可以容纳所有的会议。

排序部分很容易,但对于每个会议,我们如何高效地找出一个房间是否可用呢?在任何时间点,我们都可能有多个房间可以占用,我们并不真正关心哪个房间是空闲的,只要我们找到一个就行。

一个检查房间是否可用的简单方法是遍历所有的房间,看看是否有一个房间是空闲的。

然而,我们可以通过优先级队列或最小堆数据结构来做得更好。

不是手动遍历每个已分配的房间并检查房间是否可用,我们可以将所有的房间放在一个最小堆中,其中最小堆的键是会议的结束时间。
因此,每次我们想要检查 任何 房间是否空闲时,只需要检查最小堆顶部的元素,因为这将是当前占用的所有其他房间中最早空闲的房间。
如果从最小堆顶部提取的房间不空闲,那么 没有其他房间是空闲的。所以我们在这里可以节省时间,只需分配一个新的房间。

算法实现

  1. 按其开始时间对给定的会议进行排序。
  2. 初始化一个新的 min-heap 并将第一个会议的结束时间添加到堆中。我们只需要跟踪结束时间,因为这告诉我们什么时候一个会议室会空闲。
  3. 对于每个会议室,检查堆的最小元素即堆顶的房间是否空闲。
  4. 如果房间是空闲的,那么我们提取最顶部的元素并加入当前正在处理的会议的结束时间。
  5. 如果不是空闲的,那么我们分配一个新的房间并将其添加到堆中。
  6. 处理完所有的会议后,堆的大小会告诉我们分配了多少个房间。这将是容纳所有会议所需的最小房间数。

Java版:

class Solution {
    public int minMeetingRooms(int[][] intervals) {
        Arrays.sort(intervals, new Comparator<int[]>() {
            public int compare(int[] a, int[] b) {
                return a[0] - b[0];
            }
        });

        PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>() {
            public int compare(Integer a, Integer b) {
                return a - b;
            }
        });

        heap.add(intervals[0][1]);
        for (int i = 1; i < intervals.length; i++) {
            if (heap.peek() <= intervals[i][0]) {
                heap.poll();
            }
            heap.add(intervals[i][1]);
        }
        return heap.size();
    }
}

Python3版:

class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda x: x[0])
        heap = []
        heap.append(intervals[0][1])
        for i in range(1, len(intervals)):
            if heap[0] <= intervals[i][0]:
                heappop(heap)
                
            heappush(heap, intervals[i][1])
        return len(heap)

复杂性分析

  • 时间复杂性:O(NlogN)。这里有两个主要部分需要花费时间。一是 排序 的部分,考虑到数组包含了 N 个元素,所以时间复杂度为 O(NlogN)。然后我们还有 min-heap。在最坏的情况下,所有 N 个会议会相互冲突。无论如何,我们需要在堆中添加 N 个操作。在最坏的情况下,我们还会有 N 个提取最小值的操作。总体来说,复杂性为 (NlogN),因为在堆上进行提取最小值操作需要 O(logN)。
  • 空间复杂性:O(N),因为我们构建了 min-heap,并且在最坏的情况下,它可能包含 N 个元素,就像我们在时间复杂性部分描述的那样。因此,空间复杂性为 O(N)。

解法3:按时间顺序排序

会议时间给我们定义了一天中活动的时间顺序。我们得到的是每个会议的开始和结束时间,这可以帮助我们定义这个顺序。
按照会议的开始时间进行排列可以帮助我们了解一天之内的会议的自然顺序。然而,仅仅知道会议开始的时间并不能告诉我们会议的时长。
我们还需要按结束时间来排序会议,因为会议的结束事件本质上表示肯定有一次相应的开始事件,更重要的是,会议结束表示一个之前被占用的房间现在已经空闲。
一个会议由它的开始时间和结束时间来定义。然而,对于这个特定的算法,我们需要将开始时间和结束时间 单独处理。这可能一开始不太清楚,因为一个会议由它的开始时间和结束时间来定义。如果我们分开处理它们,并对两者进行个别处理,那么会议的身份就会消失。这是没问题的,因为:

当我们遇到一个结束事件时,这意味着稍早开始的某个会议现在已经结束了。我们并不真正关心结束的是哪个会议。我们需要的只是 某个 会议已经结束,因此一个房间已经变得可用。

让我们考虑跟上一个方法一样的例子。我们有以下会议需要安排:(1,10),(2,7),(3,19),(8,12),(10,20),(11,30)。和之前一样,第一个图显示了前三个会议,它们需要单独的房间来进行,因为它们的时间会相互冲突。

接下来的两个图显示了剩下的会议的处理过程,我们看到我们现在可以重用一些现有的会议室。最终的结果是一样的,我们需要4 个不同的会议室来处理所有的会议。这是我们能做到的最好的结果。

算法

  1. 将开始时间和结束时间分开放在它们各自的数组中。
  2. 分别对开始时间和结束时间进行排序。注意,这会破坏原来开始时间和结束时间的对应关系。它们将被单独处理。
  3. 我们考虑两个指针:s_ptr 和 e_ptr,它们分别指向开始指针和结束指针。开始指针简单地遍历所有的会议,结束指针帮助我们跟踪会议是否结束,我们是否可以重用房间。
  4. 当考虑一个被 s_ptr 所指向的特定会议时,我们检查这个开始时间是否大于被 e_ptr 所指向的会议。如果是这样,那就意味着在 s_ptr处需要开始的会议之前,一些会议已经结束了。所以我们可以重复使用其中一个房间。否则,我们必须分配一个新的房间。
  5. 如果一个会议确实结束了,即 start[s_ptr]>=end[e_ptr],那么我们让 e_ptr 加 1。
  6. 重复这个过程,直到 s_ptr 处理了所有的会议。

Java版:

class Solution {
    public int minMeetingRooms(int[][] intervals) {
        int n = intervals.length;
        int[] start = new int[n];
        int[] end = new int[n];
        for (int i = 0; i < n; i++) {
            start[i] = intervals[i][0];
            end[i] = intervals[i][1];
        }

        Arrays.sort(start);
        Arrays.sort(end);

        int cnt = 1;
        int s = 1;
        int e = 0;
        while (s < n) {
            if (start[s] >= end[e]) {
                cnt--;
                e++;
            }
            cnt++;
            s++;
        }
        return cnt;
    }
}

Python3版:

class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        n = len(intervals)
        start = sorted([i[0] for i in intervals])
        end = sorted([i[1] for i in intervals])
        
        cnt = 1
        s = 1
        e = 0
        while s < n:
            # 如果有一个会议在 `start_pointer` 开始时已经结束
            if start[s] >= end[e]:
                # 释放一个房间并递增end_pointer
                cnt -= 1
                e += 1
            
            # 无论房间是否空闲,我们都会这样做。
            # 如果一个房间是空闲的,那么 cnt += 1 将不会有任何效果
            # cnt 在这种情况下会保持不变。如果没有空闲的房间,则会增加已用房间数。
            cnt += 1
            s += 1
        return cnt

复杂性分析

  • 时间复杂性:O(NlogN) 因为我们做的只是分别为 start timings 和 end timings 两个数组进行排序,每个数组都会包含 N 个元素,考虑到有 N 个时间段。
  • 空间复杂性:O(N) 因为我们创建了两个大小为 N的独立数组,一个用于记录开始时间,一个用于记录结束时间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值