给定一个区间的集合
intervals,其中intervals[i] = [starti, endi]。返回 需要移除区间的最小数量,使剩余区间互不重叠 。注意 只在一点上接触的区间是 不重叠的。例如
[1, 2]和[2, 3]是不重叠的。
这个问题是 LeetCode 435 题。要解决它,最核心的直觉不是去思考“要删除哪些”,而是转过头思考: “我最多能保留多少个互不重叠的区间?”
总数减去最多能保留的数量,就是我们需要移除的最小数量。
1. 通俗易懂的解题思路:贪心算法
想象你在安排一个教室的课程,为了在一天内排进尽可能多的课,你的策略应该是:哪节课结束得早,就先排哪节。
为什么?因为一节课结束得越早,留给后面课程的时间就越多。
具体步骤:
- 按“右边界”(结束时间)排序:把所有区间按照结束时间从小到大排个序。
- 选出第一节课:排好序后,第一个区间的结束时间是最早的,我们一定选它。
- 找下一节不冲突的课:遍历剩下的区间,如果某个区间的“开始时间”大于或等于上一个选定区间的“结束时间”,说明不重叠,把它也选进来。
- 计数:记录下一共选了多少个区间。
2. 关键判定条件
在遍历过程中,我们只需要比较两个值:
- 当前区间的开始时间 (
intervals[i][0]) - 上一个选中区间的结束时间 (
rightBoundary)
判定公式: > 如果
intervals[i][0] >= rightBoundary,则代表不重叠,可以保留。
3. JavaScript 代码实现
JavaScript
/**
* @param {number[][]} intervals
* @return {number}
*/
var eraseOverlapIntervals = function(intervals) {
if (intervals.length === 0) return 0;
// 1. 关键:按照每个区间的“结束时间”进行升序排序
intervals.sort((a, b) => a[1] - b[1]);
let count = 1; // 能够保留的区间数量,至少为 1
let rightBoundary = intervals[0][1]; // 当前选定区间的结束时间
for (let i = 1; i < intervals.length; i++) {
// 2. 关键判定:如果当前区间的开始时间 >= 上一个保留区间的结束时间
if (intervals[i][0] >= rightBoundary) {
count++; // 找到一个不重叠的,保留它
rightBoundary = intervals[i][1]; // 更新右边界
}
}
// 3. 最终结果 = 总长度 - 最多能保留的数量
return intervals.length - count;
};
4. 为什么不能按“左边界”排序?
这是一个常见的坑。
- 如果按左边界排序,一个很长很长的区间如果开始得很早,它可能会跨越之后的所有区间。如果你选了它,后面所有的短区间都要被删掉,这显然不是最优解。
- 按右边界排序则保证了我们每次都给后面留下了“尽可能大”的空间。
总结
- 排序依据:结束时间(右边界)。
- 核心逻辑:只要不重叠,就尽量多拿。
- 计算目标:总数 - 保留数 = 移除数。