当前:LC252 · 会议室 · 首次出现于 Day 8 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC252 · Meeting Rooms · 区间

会议室:排好序,只看隔壁有没有踩到

一个人要参加全部会议,当且仅当排序后每一场的开始都不早于上一场的结束。相碰可以,交叠不行。

按开始时间排序。若某次会议的开始时间早于前一次会议的结束时间,则两者冲突,返回 false。遍历全部相邻会议都无冲突则返回 true。O(n log n)。

时间 O(n log n)空间 O(1)结论先行 · 全文约 6 节
导读

给定若干会议的时间区间,问一个人能不能全部参加——也就是这些区间能不能两两不重叠。能则 true,有一场跟另一场抢时间则 false。

主例 [[0,30],[5,10],[15,20]]:5 点第二场要开始,第一场要开到 30,冲突,false。对照 [[7,10],[2,4]],按开始时间排成 [2,4]、[7,10],4 点结束、7 点再开,true。演示两帧分别是这两份输入。

两两检查是平方级。本文要回答:为什么按开始时间排完之后只看相邻就够,以及 [0,5] 和 [5,10] 为什么不算冲突。

缺口是相邻关系,不是全部点对

第一直觉对每两场会议比是否交叠。三场会比三对,三十场比四百多对,能做对但慢。缺的是一个顺序:把会议按开始时间排成一列之后,时间轴上它们是从左到右推进的。若存在任何交叠,必有一对在这条序列里相邻的会议交叠。

证明靠排序。设按开始时间 A、B、C…。若 A 与 C 交叠而 A 与 B 不相交,则 B.start ≥ A.end。又 C.start ≥ B.start,于是 C.start ≥ A.end,A 与 C 并不交叠,矛盾。所以不相邻的冲突会先表现为某一对邻居冲突。只扫 i 与 i-1 即可。

冲突条件是 intervals[i][0] < intervals[i-1][1]:后一场开始早于前一场结束。等号不算:一场 5 点整结束,下一场 5 点整开始,同一个人赶得上。题目把区间当成「结束瞬间可以立刻开下一场」。

用手走主例。已按开始时间排好:[0,30]、[5,10]、[15,20]。看第一对:5<30,冲突,直接 false。演示第一帧 pair=[0,1]。不必再看 [15,20]——一个人已经在 0 到 30 里和 5 到 10 打架了。

第二份输入先排序。[7,10] 挪到 [2,4] 后面。7 ≥ 4,不冲突,只有一对邻居,返回 true。演示第二帧。空列表或只有一场,没有邻居可冲突,true。

不要按结束时间排来做这题:开始时间才保证「左边那场确实更早开场」,相邻比较才对应时间轴顺序。LC435 按结束排是因为要贪心保留;LC253 要数最少房间,得用扫描线。这题只问能不能一个房间装下全部,相邻一刀就够。

开闭端点end == start 不冲突。写成 <= 会把 [0,5]、[5,10] 错判成 false。主例的冲突是 5<30,真重叠,不是端点相碰。
相邻会议:后一场开始早于前一场结束即冲突
[0,30]
[5,10]
[15,20]
[5,10] 开始于 5 < [0,30] 结束 30 → 冲突

Go:排序 + 相邻判断

solution.goGo
func canAttendMeetings(intervals [][]int) bool {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
for i := 1; i < len(intervals); i++ {
if intervals[i][0] < intervals[i-1][1] { return false }
}
return true
}

1按开始时间升序。对照例 [7,10]、[2,4] 必须先换成 [2,4]、[7,10] 再比。

2只比相邻。主例第一对 5<30 就返回,后面的 15 不用看。

3判断是 < 不是 <=。[5,10] 接 [10,15] 应返回 true。

总结

按开始排序,邻居 start < 上一场 end 则冲突。主例 5<30 为 false。

  • 排序后不相邻的重叠必先表现为相邻重叠,不必两两比。
  • 端点相等不冲突。LC435 要删最少,LC253 要数房间,都比这题多一步。
  • 时间由排序决定,O(n log n)。
同族题目
LC56合并区间LC253会议室 II(最少会议室)LC435无重叠区间