1. 扫描线算法
1)原理:
平面扫描线算法通常由扫描线、事件点、当前扫描事件点集合构成:通过扫描线按照某一方向依次扫描,扫描事件点,检查事件点状态,然后新增或删除事件点以更新事件点集合。
2)看一道经典的问题:
Lintcode391. 数飞机
给出飞机的起飞和降落时间的列表,用 interval 序列表示. 请计算出天上同时最多有多少架飞机?
这道题看起来很简单,但是我的做法超时了:
public class Solution { /** * @param airplanes: An interval array * @return: Count of airplanes are in the sky. */ public int countOfAirplanes(List<Interval> airplanes) { // write your code here if (airplanes.size() == 0) return 0; int maxTime = Integer.MIN_VALUE; for (Interval interval : airplanes) { maxTime = Math.max(maxTime, interval.end); } int[] timePoint = new int[maxTime + 1]; for (int i = 1; i <= maxTime; i++) { int showTime = 0; for (Interval interval : airplanes) { if (interval.start <= i && interval.end > i) { showTime++; } } timePoint[i] = showTime; } int res = Integer.MIN_VALUE; for (int i = 1; i <= maxTime; i++) { res = Math.max(res, timePoint[i]); } return res; } }
这个解法是很细粒度的扫描,就是每个时刻都要扫描一次,所以当最长事件很大时,我开的数据就很大,就需要扫描很长的数组。
那么可否粗粒度的扫描呢?答那是可以的,就是只需要检查起点和终点。因为一段时间的构成就是起点和终点,那么涉及到事件点集合的变化就是起点的出现和终点的出现。
那么具体是如何变化的呢?想一想,如果一个起点出现,会发生什么,就说明当前时刻又有一个时间段出现了,如果终点出现,就说明当前时刻结束了一个时间段。
那么我们可以维护一个count,来记录实时事件点的变化。 当起点出现,count++;终点出现count--;哈,前提是这些时间段应该顺序排列。
ok,按这个思路改了代码,ac
public class Solution { /** * @param airplanes: An interval array * @return: Count of airplanes are in the sky. */ class Point { int time; int flag; public Point(int t, int f) { time = t; flag = f; } } public int countOfAirplanes(List<Interval> airplanes) { // write your code here List<Point> points = new ArrayList<>(); for (Interval interval : airplanes) { points.add(new Point(interval.start, 1)); points.add(new Point(interval.end, 0)); } Collections.sort(points, new Comparator<Point>(){ public int compare(Point p1, Point p2) { if (p1.time == p2.time) return p1.flag - p2.flag; return p1.time - p2.time; } }); int res = 0, count = 0; for (Point point : points) { if (point.flag == 1) count++; else count--; res = Math.max(res, count); } return res; } }
参考资料: