会议室:排好序,只看隔壁有没有踩到
一个人要参加全部会议,当且仅当排序后每一场的开始都不早于上一场的结束。相碰可以,交叠不行。
按开始时间排序。若某次会议的开始时间早于前一次会议的结束时间,则两者冲突,返回 false。遍历全部相邻会议都无冲突则返回 true。O(n log n)。
给定若干会议的时间区间,问一个人能不能全部参加——也就是这些区间能不能两两不重叠。能则 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,真重叠,不是端点相碰。
Go:排序 + 相邻判断
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)。