问题概述
本文解决一个很经典的贪心算法问题 Interval Scheduling(区间调度问题)。给你很多形如[start,end]的闭区间,请你设计一个算法,算出这些区间中最多有几个互不相交的区间。
int intervalScheduling(int[][] ints) {}
举个例子,intvs=[[1,3],[2,4],[3,6]],这些区间最多有两个区间互不相交,即[[1,3],[3,6]],你的算法应该返回 2。注意边界相同并不算相交。
这个问题在生活中的应用广泛,比如你今天有好几个活动,每个活动都可以用区间[start,end]表示开始和结束的时间,请问你今天最多能参加几个活动呢?
思路
正确的思路其实很简单,可以分为以下三步:
- 从区间集合 intvs 中选择一个区间 x,这个 x 是在当前所有区间中结束最早的(end 最小)。
- 把所有与 x 区间相交的区间从区间集合 intvs 中删除。
- 重复步骤 1 和 2,直到 intvs 为空为止。之前选出的那些 x 就是最大不相交子集。
把这个思路实现成算法的话,可以按每个区间的end数值升序排序,因为这样处理之后实现步骤 1 和步骤 2 都方便很多:
对于步骤 1,由于我们预先按照end排了序,所以选择 x 是很容易的。关键在于,如何去除与 x 相交的区间,选择下一轮循环的 x
由于我们事先排了序,不难发现所有与 x 相交的区间必然会与 x 的end相交;如果一个区间不想与 x 的end相交,它的start必须要大于(或等于)x 的end
代码:
public int intervalSchedule(int[][] intvs) {if (intvs.length == 0) return 0;// 按 end 升序排序Arrays.sort(intvs, new Comparator<int[]>() {public int compare(int[] a, int[] b) {return a[1] - b[1];}});// 至少有一个区间不相交int count = 1;// 排序后,第一个区间就是 xint x_end = intvs[0][1];for (int[] interval : intvs) {int start = interval[0];if (start >= x_end) {// 找到下一个选择的区间了count++;x_end = interval[1];}}return count;}
435. 无重叠区间

我们已经会求最多有几个区间不会重叠了,那么剩下的不就是至少需要去除的区间吗?
class Solution {public int intervalSchedule(int[][] intvs) {if (intvs.length == 0) return 0;// 按 end 升序排序Arrays.sort(intvs, new Comparator<int[]>() {public int compare(int[] a, int[] b) {return a[1] - b[1];}});// 至少有一个区间不相交int count = 1;// 排序后,第一个区间就是 xint x_end = intvs[0][1];for (int[] interval : intvs) {int start = interval[0];if (start >= x_end) {// 找到下一个选择的区间了count++;x_end = interval[1];}}return count;}public int eraseOverlapIntervals(int[][] intervals) {int n = intervals.length;return n-intervalSchedule(intervals);}}
452. 用最少数量的箭引爆气球

这个问题和区间调度算法一模一样!如果最多有n个不重叠的区间,那么就至少需要n个箭头穿透所有区间:
只是有一点不一样,在intervalSchedule算法中,如果两个区间的边界触碰,不算重叠;而按照这道题目的描述,箭头如果碰到气球的边界气球也会爆炸,所以说相当于区间的边界触碰也算重叠:
class Solution {
public int findMinArrowShots(int[][] points) {
Arrays.sort(points, (a, b) -> {
return Integer.compare(a[1], b[1]);
});
int count = 1;
int end = points[0][1];
for (int[] point : points) {
int start = point[0];
if (start > end) { // 找到不重叠区间(边界重叠也算重叠)
count++;
end = point[1];
}
}
return count;
}
}
小结
区间调度问题思路可以分为以下三步:
1、从区间集合 intvs 中选择一个区间 x,这个 x 是在当前所有区间中结束最早的(end 最小)。
2、把所有与 x 区间相交的区间从区间集合 intvs 中删除。
3、重复步骤 1 和 2,直到 intvs 为空为止。之前选出的那些 x 就是最大不相交子集。
对于区间问题的处理,一般来说第一步都是排序,相当于预处理降低后续操作难度。但是对于不同的问题,排序的方式可能不同,这个需要归纳总结
参考
一文秒杀所有区间相关问题
