1. 题目说明
给定一组区间 intervals,返回需要移除的最少区间数量,使剩余区间互不重叠。
这里的边界规则很重要:[1, 2] 和 [2, 3] 不算重叠。
1.1 生活映射
可以把每个区间看成一场活动。因为同一时间只能参加一场,所以我们要尽量保留更多互不冲突的活动。保留得越多,需要删除的就越少。
所以本题其实是在问:
怎样保留最多的活动,才能让删掉的活动最少?
2. 贪心思路
这题可以转化为经典的区间调度问题。
我们不直接想“删掉哪些区间”,而是反过来想:
最多能保留多少个不重叠区间?
只要能算出“最多保留几个”,答案就很自然了:
需要删除的数量 = 总区间数 - 最多保留数量
做法很简单:
- 按区间右端点从小到大排序。
- 依次扫描区间。
- 先记录上一个保留区间的右端点
preEnd。如果当前区间的左端点start >= preEnd,说明它和前一个区间不重叠,就保留它,并把preEnd更新为当前区间的右端点,方便判断下一个区间。 - 否则说明重叠了,跳过它。
为什么要按右端点排序?
直觉上,结束得越早的区间,给后面的区间留下的空间越大。
更严格地说,在当前所有可选区间中,应该优先选择右端点最小的那个。
这个结论可以这样理解:
- 假设某个最优解中,第一个被选择的区间是
B - 如果在当前还可选择的区间里,存在一个结束更早或相同的区间
A - 那就可以把
B换成A
由于 A 的右端点更小,它只会给后面的区间留出更多空间,不会更差。
所以在所有最优解里,一定存在一种方案,第一步选的是右端点最小的区间。
这也是为什么按右端点排序后,扫描会特别自然:
- 排序后,第一个区间一定最早结束,先保留它
- 下一个能选的区间,就是第一个左端点
>= preEnd的区间 - 遇到重叠就跳过,遇到不重叠就保留
所以这个贪心是成立的。
这里再把变量说清楚:
count:当前已经保留了多少个区间preEnd:上一个保留区间的右端点
扫描时只需要比较当前区间的左端点和 preEnd。如果能接上,就保留当前区间,然后把 preEnd 更新成这个区间的右端点;接不上,就跳过。
这和 LeetCode 452“用最少数量的箭引爆气球”很像,区别只是边界判断不同:
435 题里start == preEnd不算重叠,452 题里要把这种情况看成仍然需要继续处理。
3. 例子
比如这些区间:
[1, 2], [2, 3], [3, 4], [1, 3]按右端点排序后,顺序其实是:
[1, 2], [1, 3], [2, 3], [3, 4]扫描过程如下:
- 先看
[1, 2],它是第一个区间,直接保留,preEnd = 2 - 接着看
[1, 3],因为1 < 2,重叠,删掉 - 再看
[2, 3],因为2 >= 2,保留,preEnd = 3 - 最后看
[3, 4],因为3 >= 3,也保留
最终保留 3 个,删除 1 个。
4. Java 实现
import java.util.Arrays;
class Solution { public int eraseOverlapIntervals(int[][] intervals) { if (intervals == null || intervals.length == 0) { return 0; }
// 按右端点升序排序 Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
int count = 1; // 第一个区间一定先保留 int preEnd = intervals[0][1];
for (int i = 1; i < intervals.length; i++) { // 边界相接不算重叠 if (intervals[i][0] >= preEnd) { count++; preEnd = intervals[i][1]; } }
return intervals.length - count; }}这段代码的逻辑就是:
- 先把结束最早的区间保留下来
- 以后每来一个区间,就看它能不能接在
preEnd后面 - 能接上就保留,不能接上就放弃
这样扫描一遍,就能得到最多能保留多少个区间。
4.1 左端点排序也可以吗
可以,但写法会略有不同。
如果按左端点排序,就不是去统计“最多保留多少个”,而是直接统计“发生了多少次重叠”。
核心做法是:
- 遇到重叠时,保留右端点更小的那个区间
- 这样能给后面的区间留下更大空间
对应代码大概是这样:
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
int remove = 0;int preEnd = intervals[0][1];
for (int i = 1; i < intervals.length; i++) { if (intervals[i][0] < preEnd) { remove++; preEnd = Math.min(preEnd, intervals[i][1]); } else { preEnd = intervals[i][1]; }}这里最容易写错的地方是:一旦重叠,就要把 preEnd 更新成两个右端点里的较小值。这样相当于保留了“更短”的那个区间。
思路和右端点排序本质一致,只是实现角度不同。本文这里推荐右端点排序,直观且更容易写对。
5. 复杂度分析
- 时间复杂度:
O(n log n),主要来自排序。 - 空间复杂度:
O(log n)或O(n),取决于排序实现。
6. 总结
这题只要抓住一句话就够了:
按右端点排序,优先保留结束最早的区间。
这样留下的空间最多,也就最容易保留更多区间,最后删除数自然最少。
如果再记一句实现口令,就是:
preEnd记录上一个保留区间的右端点,遇到start < preEnd就跳过当前区间。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时


















