问题简介
题解github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions
题目描述
给你一个会议时间安排的数组 intervals,其中 intervals[i] = [starti, endi] 表示会议 i 的开始和结束时间,请你返回至少需要多少间会议室。
示例说明
✅ 示例 1:
输入: intervals = [[0,30],[5,10],[15,20]]
输出: 2
解释:
- 会议 [0,30] 占用会议室 1
- 会议 [5,10] 与 [0,30] 冲突,需会议室 2
- 会议 [15,20] 可复用会议室 2(因为 [5,10] 已结束)
✅ 示例 2:
输入: intervals = [[7,10],[2,4]]
输出: 1
解释: 两个会议时间不重叠,可共用一间会议室。
解题思路
💡 方法一:优先队列(最小堆)------ 推荐解法
核心思想 :按会议开始时间排序,用最小堆维护当前所有会议室的结束时间。当新会议到来时:
- 如果最早结束的会议已结束(堆顶 ≤ 当前会议开始时间),则复用该会议室(弹出堆顶,压入新结束时间)
- 否则,新开一间会议室(直接压入新结束时间)
步骤详解:
- 将所有会议按
start升序排序 - 初始化最小堆(存储会议结束时间)
- 遍历每个会议:
- 若堆非空且堆顶 ≤ 当前会议
start→ 弹出堆顶(释放会议室) - 将当前会议
end压入堆
- 若堆非空且堆顶 ≤ 当前会议
- 最终堆大小即为所需会议室数
💡 方法二:上下车模型(扫描线)
核心思想:将每个会议拆分为两个事件:
(start, +1):上车(需要会议室)(end, -1):下车(释放会议室)
按时间排序所有事件,遍历时累加变化值,最大累加值即为答案。
关键细节 :若时间相同,先处理下车事件(-1),再处理上车事件(+1),避免多算。
💡 方法三:双指针(分离起点终点)
核心思想:
- 分别提取所有
start和end时间并排序 - 用双指针遍历:
start[i] < end[j]→ 需要新会议室(count++,i++)- 否则 → 释放会议室(
j++)
- 记录
count的最大值
代码实现
java
// 方法一:优先队列(最小堆)
class Solution {
public int minMeetingRooms(int[][] intervals) {
if (intervals.length == 0) return 0;
// 按开始时间排序
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// 最小堆存储结束时间
PriorityQueue<Integer> heap = new PriorityQueue<>();
heap.offer(intervals[0][1]);
for (int i = 1; i < intervals.length; i++) {
// 如果最早结束的会议已结束,复用会议室
if (heap.peek() <= intervals[i][0]) {
heap.poll();
}
heap.offer(intervals[i][1]);
}
return heap.size();
}
}
// 方法二:上下车模型
class Solution {
public int minMeetingRooms(int[][] intervals) {
List<int[]> events = new ArrayList<>();
for (int[] interval : intervals) {
events.add(new int[]{interval[0], 1}); // 上车
events.add(new int[]{interval[1], -1}); // 下车
}
// 排序:时间相同时,先下车(-1)后上车(1)
events.sort((a, b) -> {
if (a[0] != b[0]) return a[0] - b[0];
return a[1] - b[1];
});
int count = 0, maxRooms = 0;
for (int[] event : events) {
count += event[1];
maxRooms = Math.max(maxRooms, count);
}
return maxRooms;
}
}
go:Go
// 方法一:优先队列(最小堆)
import "sort"
type MinHeap []int
func (h MinHeap) Len() int { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MinHeap) Push(x interface{}) { *h = append(*h, x.(int)) }
func (h *MinHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
func minMeetingRooms(intervals [][]int) int {
if len(intervals) == 0 {
return 0
}
// 按开始时间排序
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
// 最小堆存储结束时间
heap := &MinHeap{intervals[0][1]}
for i := 1; i < len(intervals); i++ {
// 如果最早结束的会议已结束,复用会议室
if (*heap)[0] <= intervals[i][0] {
heap.Pop()
}
heap.Push(intervals[i][1])
}
return heap.Len()
}
// 方法二:上下车模型
func minMeetingRooms(intervals [][]int) int {
type Event struct {
time int
diff int // +1 上车, -1 下车
}
events := make([]Event, 0, len(intervals)*2)
for _, interval := range intervals {
events = append(events, Event{interval[0], 1})
events = append(events, Event{interval[1], -1})
}
// 排序:时间相同时,先下车(-1)后上车(1)
sort.Slice(events, func(i, j int) bool {
if events[i].time != events[j].time {
return events[i].time < events[j].time
}
return events[i].diff < events[j].diff
})
count, maxRooms := 0, 0
for _, e := range events {
count += e.diff
if count > maxRooms {
maxRooms = count
}
}
return maxRooms
}
示例演示
📌 以 [[0,30],[5,10],[15,20]] 为例(方法一):
| 步骤 | 当前会议 | 堆状态(结束时间) | 操作 | 会议室数 |
|---|---|---|---|---|
| 初始 | - | [30] |
加入 [0,30] | 1 |
| 1 | [5,10] | [10, 30] |
5 < 30 → 新开会议室 | 2 |
| 2 | [15,20] | [20, 30] |
15 ≥ 10 → 复用(弹出10) | 2 |
✅ 最终结果:2
答案有效性证明
✅ 方法一正确性
- 贪心选择:总是复用最早结束的会议室,保证资源最优利用
- 数学归纳 :假设前
k个会议已最优分配,第k+1个会议若能复用则必复用(否则浪费资源),若不能则必须新开
✅ 方法二正确性
- 事件离散化:将连续时间转化为离散事件点
- 差分思想 :
+1/-1精确模拟资源占用变化 - 排序规则 :时间相同时先释放再占用,避免瞬时多占(如
[1,2],[2,3]只需1间)
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 优先队列 | O(n log n) | O(n) | 排序 O(n log n) + 堆操作 O(n log n) |
| 上下车模型 | O(n log n) | O(n) | 排序 2n 个事件 |
| 双指针 | O(n log n) | O(n) | 排序两个数组 |
💡 最优实践:优先队列法最直观,上下车模型代码更简洁,双指针空间常数更小。
问题总结
| 关键点 | 说明 |
|---|---|
| ❌ 常见错误 | 忽略时间相同时的处理顺序(如 [1,2],[2,3] 误判为冲突) |
| ✅ 核心洞察 | 会议室需求 = 同时进行的最大会议数 |
| 💡 扩展思考 | 若需返回具体分配方案?→ 需记录每间会议室的使用历史 |
| 📌 面试提示 | 优先讲解优先队列法(直观),再提及其他方法展示思维广度 |
一句话总结 :"按开始时间排序,用最小堆跟踪最早空闲的会议室" 是解决区间调度类问题的经典范式。