目录

题目描述

253. 会议室 II

题意分析

输入是一组会议的起止时间 [start, end],每个会议必须独占一间会议室,问最少准备几间会议室才能把所有会议都排下。

关键是把「最少几间」翻译成一个可计算的量:某一时刻同时进行的会议有几场,那一刻就至少要几间会议室;反过来,只要房间数达到了整条时间线上的最大同时进行数,就一定排得下——任意时刻的需求都不超过这个上限,永远有空房可分。所以答案等于区间的最大重叠数,一个纯统计量,与「具体把哪场会排进哪间屋」无关。

约束信号是区间半开:[0, 30][30, 40] 不算冲突,前一场结束的瞬间房间已经腾出来了。这决定了后面所有比较里,端点相等必须判为「可复用」。

边界要留意:会议列表为空时答案为 0;只有一场会议时答案为 1;输入不保证按开始时间有序,也不保证区间互不相同,两场完全重合的会议仍要占两间房。

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

核心思路

最少会议室数等于任意时刻的最大并发会议数:并发数给出房间数的下界;准备这么多房间后,每次会议结束就释放一间,也足以安排后续会议。

并发数只会在端点处变化,因此把开始时间和结束时间分别排序,用两个指针按时间合并事件。若下一个开始时间早于下一个结束时间,说明旧会议尚未结束,新会议必须多占一间,active++;否则先处理结束事件并释放房间,active--。端点相等时也应先结束再开始,因为 [0,10][10,20] 可以复用同一间房。

扫描中的不变量是:active = 已处理的开始事件数 - 已处理的结束事件数,即当前正在进行的会议数;answer 保存它的历史最大值。我们只统计事件数量,不关心某个开始时间对应哪个结束时间,所以拆开区间不会丢失答案所需的信息。

解题步骤

  1. 将所有区间拆成 startsends,分别升序排序。
  2. 比较两个指针指向的下一个事件:开始更早就增加并发数,否则减少并发数。
  3. 只有开始事件会抬高峰值,此时更新答案;对应指针随后右移。
  4. 所有开始事件处理完后返回最大并发数。

[[0,30],[5,10],[15,20]],开始序列为 [0,5,15],结束序列为 [10,20,30]。合并出的关键事件是“开始、开始、结束、开始”,active 依次为 1、2、1、2,峰值为 2。

代码实现

import java.util.Arrays;

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)$,保存两个端点数组。

关键点总结

  • 最少资源数问题先转化为“最大同时占用数”,无需真的分配房间。
  • 双指针本质是合并两组有序事件,active 始终表示当前重叠数。
  • 相同时间必须先处理结束事件,才能正确复用会议室。
  • 若题目要求输出具体的房间分配,才需要改用最小堆保存房间结束时间。

易错点总结

  • starts[startIdx] <= ends[endIdx] 判断开始事件,会把相接区间误判为重叠;[[0,10],[10,20]] 只需一间房。
  • 任一端点数组未排序,两个指针就不再代表全局下一个事件;[[10,20],[0,5]] 应返回 1。
  • 返回扫描结束时的 active 而不是历史峰值,会漏掉中途并发;[[0,30],[5,10],[40,50]] 的峰值是 2。
  • 空输入时不能访问 ends[0],应先返回 0。

相似题目

题目 难度 考察点
252. 会议室 简单 只需判断是否存在重叠,排序后比较相邻区间即可
56. 合并区间 中等 要输出合并后的区间本身,必须保留配对关系
435. 无重叠区间 中等 求最少删除数,按结束时间排序做贪心而非统计重叠
1094. 拼车 中等 事件带权重,重叠数换成载客量之和,天然适配差分
1109. 航班预订统计 中等 纯差分数组模板,求的是每个位置的最终值而非峰值
732. 我的日程安排表 III 困难 区间在线到来,需用有序表或线段树动态维护最大重叠数
759. 员工空闲时间 困难 求重叠数降为 0 的空档区间,扫描线的输出形态不同
1229. 安排会议日程 中等 求两组区间的交集且需满足时长下限,用双指针求交而非计数