题目描述:
给定一个会议时间安排的数组
intervals,每个会议时间包括开始和结束的时间[start, end],计算至少需要多少间会议室才能满足所有会议安排。示例:
输入: [[0, 30], [5, 10], [15, 20]] 输出: 2 输入: [[7, 10], [2, 4]] 输出: 1
解题思路:
方法一:排序 + 小根堆(推荐)
核心思路:
-
按开始时间排序所有会议
-
用小根堆 维护当前正在使用的会议室的结束时间
-
遍历每个会议:
-
如果堆顶(最早结束的会议)≤ 当前会议的开始时间,说明有会议室空闲,弹出堆顶
-
把当前会议的结束时间压入堆
-
-
堆的大小就是所需的最少会议室数量
具体过程示例:
intervals = [[0, 30], [5, 10], [15, 20]]
排序后:[[0, 30], [5, 10], [15, 20]]
| 会议 | 堆操作 | 堆内容 | 说明 |
|---|---|---|---|
| 0, 30 | push(30) | 30 | 第一间会议室 |
| 5, 10 | 堆顶30 > 5,push(10) | 10, 30 | 需要第二间 |
| 15, 20 | 堆顶10 ≤ 15,pop,push(20) | 20, 30 | 复用第一间 |
堆大小 = 2 ✅
代码实现:
cpp
class Solution {
public:
int minMeetingRooms(vector<vector<int>>& intervals) {
if (intervals.empty()) return 0;
// 按开始时间排序
sort(intervals.begin(), intervals.end());
// 小根堆,存储结束时间
priority_queue<int, vector<int>, greater<int>> pq;
for (auto& interval : intervals) {
// 如果有会议室空闲,弹出最早结束的
if (!pq.empty() && pq.top() <= interval[0]) {
pq.pop();
}
pq.push(interval[1]);
}
return pq.size();
}
};
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 排序 + 每个会议一次堆操作 |
| 空间复杂度 | O(n) | 堆最多存储 n 个结束时间 |
方法二:扫描线
思路:
把每个会议拆成两个事件:
-
开始:+1(需要一间会议室)
-
结束:-1(释放一间会议室)
按时间排序所有事件,累加计数,最大值就是所需会议室数。
关键 :同一时间有开始和结束,结束优先处理(先释放再占用)。
具体过程示例:
intervals = [[0, 30], [5, 10], [15, 20]]
事件:(0,+1), (5,+1), (10,-1), (15,+1), (20,-1), (30,-1)
| 时间 | 事件 | 计数 | 最大值 |
|---|---|---|---|
| 0 | +1 | 1 | 1 |
| 5 | +1 | 2 | 2 |
| 10 | -1 | 1 | 2 |
| 15 | +1 | 2 | 2 |
| 20 | -1 | 1 | 2 |
| 30 | -1 | 0 | 2 |
结果:2 ✅
代码实现:
cpp
class Solution {
public:
int minMeetingRooms(vector<vector<int>>& intervals) {
vector<pair<int, int>> events;
for (auto& interval : intervals) {
events.push_back({interval[0], 1}); // 开始
events.push_back({interval[1], -1}); // 结束
}
// 按时间排序,同一时间结束优先(-1 在前)
sort(events.begin(), events.end(), [](const pair<int,int>& a, const pair<int,int>& b) {
if (a.first == b.first) return a.second < b.second;
return a.first < b.first;
});
int count = 0, maxRooms = 0;
for (auto& [time, delta] : events) {
count += delta;
maxRooms = max(maxRooms, count);
}
return maxRooms;
}
};
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 排序事件 |
| 空间复杂度 | O(n) | 事件数组 |
方法三:双指针
思路:
把开始时间和结束时间分别排序,用两个指针比较:
-
如果
start[s] < end[e]:新会议开始,需要新会议室,count++,s++ -
否则:有会议结束,释放会议室,
count--,e++
代码实现:
cpp
class Solution {
public:
int minMeetingRooms(vector<vector<int>>& intervals) {
int n = intervals.size();
vector<int> starts(n), ends(n);
for (int i = 0; i < n; i++) {
starts[i] = intervals[i][0];
ends[i] = intervals[i][1];
}
sort(starts.begin(), starts.end());
sort(ends.begin(), ends.end());
int s = 0, e = 0, count = 0, maxRooms = 0;
while (s < n) {
if (starts[s] < ends[e]) {
count++;
s++;
} else {
count--;
e++;
}
maxRooms = max(maxRooms, count);
}
return maxRooms;
}
};
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 排序两个数组 |
| 空间复杂度 | O(n) | 两个辅助数组 |
三种方法对比:
| 方法 | 时间复杂度 | 空间复杂度 | 代码复杂度 | 推荐度 |
|---|---|---|---|---|
| 排序 + 小根堆 | O(n log n) | O(n) | 中等 | ⭐⭐⭐⭐⭐ |
| 扫描线 | O(n log n) | O(n) | 中等 | ⭐⭐⭐⭐ |
| 双指针 | O(n log n) | O(n) | 简单 | ⭐⭐⭐⭐ |
总结:
| 要点 | 说明 |
|---|---|
| 核心思想 | 求重叠会议的最大数量 |
| 最优解法 | 排序 + 小根堆(维护结束时间) |
| 关键操作 | 堆顶 ≤ 当前开始时间 → 复用会议室 |
| 时间复杂度 | O(n log n) |
| 空间复杂度 | O(n) |