1、定义
计算几何中,扫描线算法(Sweep Line Algorithm)或平面扫描算法(Plane Sweep Algorithm)是一种算法模式,虚拟扫描线或扫描面来解决欧几里德空间中的各种问题,一般被用来解决图形面积,周长等问题,是计算几何中的关键技术之一。
这种算法背后的想法是想象一条线(通常是一条垂直线)在平面上扫过或移动,在某些点停止。几何操作仅限于几何对象,无论何时停止,它们都与扫描线相交或紧邻扫描线,并且一旦线穿过所有对象,就可以获得完整的解。
2、个人理解
其实说起来比较简单,就是用一条线(横着的或者竖着的)从上到下,或者从左到右,这么水平扫描过去,需要记录中间状态,扫描完就能够得到最终结果,扫描线可以实现降维操作,类似于三维变成二维。思想是这个思想。
解题套路:
- 数据排序
- 扫描排序后的数据,聚合有共性的数据
- 用一个新的数据结构存聚合后的结果
3、做几道题来沉淀下
1、天际线问题
/**
* 天际线 @link https://leetcode.cn/problems/the-skyline-problem/
*/
public class TheSkylineProblem {
public List<List<Integer>> getSkyline(int[][] buildings) {
// 降维,
List<int[]> buildingList = new ArrayList<>();
for (int[] building : buildings) {
int[] leftBuild = new int[2];
leftBuild[0] = building[0];
leftBuild[1] = -building[2];
buildingList.add(leftBuild);
int[] rightBuild = new int[2];
rightBuild[0] = building[1];
rightBuild[1] = building[2];
buildingList.add(rightBuild);
}
// 排个序,按X从小到大排,如果X相同,则比较高度,也是从小到大
buildingList.sort((o1, o2) -> {
if (o1[0] - o2[0] == 0) {
return o1[1] - o2[1];
} else {
return o1[0] - o2[0];
}
});
List<List<Integer>> res = new ArrayList<>();
// 最大堆,记录高度状态
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Comparator.reverseOrder());
int maxHeight = 0;
// 结果必定是有个0值的
priorityQueue.add(maxHeight);
for (int[] build : buildingList) {
// 左边界那么将这个高度入队,右边界删除即可
if (build[1] < 0) {
priorityQueue.add(-build[1]);
} else {
priorityQueue.remove(build[1]);
}
// 与当前最高度不同,则收集结果
if (!priorityQueue.isEmpty() && maxHeight != priorityQueue.peek()) {
maxHeight = priorityQueue.peek();
List<Integer> tempList = new ArrayList<>();
tempList.add(build[0]);
tempList.add(maxHeight);
res.add(tempList);
}
}
return res;
}
}
2、矩形面积 II
扫描线经典问题
package geometry.sweepline;
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import java.util.*;
/**
* 矩形面积2, https://leetcode.cn/problems/rectangle-area-ii/description/
* 比较常见的利用扫描线来计算面积
*
* 竖着扫描,用区间Y的高度来作为扫描线,通过计算每个相邻的X差值*区间Y高度差,来收集面积结果
* 中间状态就是:
* 维护Y的区间高度,如果比上一个区间高就更新 Y的区间值
* 同时也维护一个Y区间高度的差值
*
* 需要注意精度值。。。 用 int 收集结果会超出
*/
public class RectangleAreaII {
public int rectangleArea(int[][] rectangles) {
// 降维,收集X坐标,并根据从小到大排序
List<Integer> xList = new ArrayList<>();
for (int[] rectangle : rectangles) {
xList.add(rectangle[0]);
xList.add(rectangle[2]);
}
Collections.sort(xList);
int MOD = (int) (1.0E9 + 7);
int res = 0;
for (int i = 1; i < xList.size(); i++) {
int xLeft = xList.get(i - 1);
int xRight = xList.get(i);
int interval = xRight - xLeft;
if (interval == 0) {
continue;
}
List<int[]> yList = new ArrayList<>();
for (int[] rectangle : rectangles) {
// 搞错了,这rectangle[2] 要大于 XRight 才记录,表示当前矩形区域覆盖 【xLeft,xRight】 这区域,最差也要是当前区域覆盖自己,所以有个=
if (rectangle[0] <= xLeft && rectangle[2] >= xRight) {
yList.add(new int[]{rectangle[1], rectangle[3]});
}
}
yList.sort((o1, o2) -> o1 != o2 ? o1[0] - o2[0] : o1[1] - o2[1]);
long yTotal = 0;
int lastDownY = -1;
int lastUpY = -1;
for (int[] ys : yList) {
// 如果当前最低的高度都高于上次最高高度,那么更新全部信息
if (ys[0] > lastUpY) {
// 先计算高度差值再赋值
yTotal += lastUpY - lastDownY;
lastDownY = ys[0];
lastUpY = ys[1];
} else if (ys[1] > lastUpY){
// 只是当前最高高于上次最高
lastUpY = ys[1];
}
}
yTotal += lastUpY - lastDownY;
long value = yTotal * interval;
res += value;
res = res % MOD;
}
return res;
}
public static void main(String[] args) {
List<int[]> rectangles = new ArrayList<>();
rectangles.add(new int[]{0,0,2,2});
rectangles.add(new int[]{1,0,2,3});
rectangles.add(new int[]{1,0,3,1});
RectangleAreaII rectangleAreaII = new RectangleAreaII();
System.out.println("res:" + rectangleAreaII.rectangleArea(rectangles.toArray(new int[][]{})));
}
}