题目描述

✅ 253. 会议室 II

题意分析

给定所有会议的开始和结束时间,安排足够多的会议室,使每场会议都能按原定时间进行;同一房间不能同时召开两场会议。返回需要的最少房间数,不要求返回具体分配方案。

一场会议结束的时刻,房间就能立刻供另一场会议使用,所以结束时间等于下一场开始时间不算冲突。会议时段本身固定,不能靠移动、拆分或取消会议减少房间。

解法:开始结束时间双指针

核心思路

[!blue]

同一时刻若有 r 场会议进行,就至少需要 r 个房间,因此最大同时进行数是答案的下界。这个下界也足够:按时间处理会议,结束就释放房间,开始就使用一个空闲房间;只要房间总量达到历史最大同时进行数,每次开始时就一定有足够空间。

计算同时进行数只关心“某时刻增加一场”或“减少一场”,不需要知道开始、结束属于哪个房间。把全部开始时间和结束时间分别排序,再用两个指针合并事件顺序,就能沿时间轴统计占用变化。

startIdx、endIdx 分别表示已处理的开始、结束事件数,active = startIdx - endIdx 表示当前占用数。下一个开始严格早于下一个结束时,先增加占用并更新峰值;否则先处理结束,释放一个房间。

同时发生的结束事件必须先处理,才能让随后开始的会议复用房间。所有开始事件处理完后,只剩释放事件,占用数不可能再创新高,因此扫描可以结束,返回记录的峰值,而不是最后的 active。

解题步骤

  1. 没有会议时返回 0;否则将开始、结束时间分别存入两个数组并升序排序。
  2. 初始化两个事件指针、当前占用 active 和历史峰值 answer 为零。
  3. 当前开始时间严格小于当前结束时间时,增加 active,更新 answer,推进开始指针。
  4. 其他情况先减少 active,推进结束指针;时间相等也走这个分支。
  5. 全部开始事件处理完后,返回最大同时占用数 answer。

代码实现

class Solution {
    public int minMeetingRooms(int[][] intervals) {
        int n = intervals.length;

        if (n == 0) {
            return 0;
        }

        int[] starts = new int[n];
        int[] ends = new int[n];

        for (int i = 0; i < n; i++) {
            starts[i] = intervals[i][0];
            ends[i] = intervals[i][1];
        }

        Arrays.sort(starts);
        Arrays.sort(ends);
        int startIdx = 0;
        int endIdx = 0;
        int active = 0;
        int answer = 0;

        while (startIdx < n) {
            // 开始与结束同刻时先释放房间,严格早于结束才新增占用。
            if (starts[startIdx] < ends[endIdx]) {
                active++;
                answer = Math.max(answer, active);
                startIdx++;
            } else {
                active--;
                endIdx++;
            }
        }

        return answer;
    }
}
import "sort"

func minMeetingRooms(intervals [][]int) int {
    n := len(intervals)
    if n == 0 {
        return 0
    }
    starts := make([]int, n)
    ends := make([]int, n)
    for i, interval := range intervals {
        starts[i] = interval[0]
        ends[i] = interval[1]
    }

    sort.Ints(starts)
    sort.Ints(ends)
    startIdx := 0
    endIdx := 0
    active := 0
    answer := 0
    for startIdx < n {
        // 开始与结束同刻时先释放房间,严格早于结束才新增占用。
        if starts[startIdx] < ends[endIdx] {
            active++
            if active > answer {
                answer = active
            }
            startIdx++
        } else {
            active--
            endIdx++
        }
    }
    return answer
}

复杂度分析

  • 时间复杂度:$O(n\log n)$,两个端点数组排序占主导;每个事件指针只向前移动,扫描为 $O(n)$。
  • 空间复杂度:$O(n)$,创建两个长度为会议数的端点数组。

关键点总结

[!green]

  • 最大同时进行数既是房间数的必要下界,也能通过及时复用房间达到。
  • 拆开端点只丢掉会议身份,不丢失同时占用数所需要的事件信息。
  • 相同时间先结束后开始,准确表达会议室可以立即复用的规则。
  • 保存历史峰值,后续释放不会抹去此前需要过的房间数量。

易错点总结

[!yellow]

  • 把开始事件的判断写成 <=,相同时间会先占用后释放,虚增房间数。
  • 端点数组没有分别排序,两个指针就不能代表下一件按时间发生的事件。
  • 返回最后的 active,忽略了此前可能出现过更高的同时占用数。
  • 混淆事件计数与会议分配,认为开始端点和结束端点拆开就无法求解;数量只取决于累积增减。
  • 空输入仍读取第一个结束时间会越界,已有的提前返回需要保留。

相似题目

题目 难度 关联与区别
252. 会议室 简单 只用一间房等价于全部区间无重叠,本题要求计算所有时刻的最大并发数。
732. 我的日程安排表 III 困难 同样维护区间最大重叠数量,原题日程逐次加入,本题可离线排序全部事件。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/86099077
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!