DeepSeek LeetCode 850.矩形面积|| public int rectangleArea(int[][] rectangles)
下面是 LeetCode 850 题「矩形面积 II」的 Java 解法。该解法使用扫描线 + 离散化 + 线段树,时间复杂度 O(n log n),空间复杂度 O(n)。· 线段树更新时,区间为 [y1Idx, y2Idx-1],因为离散化下标 i 对应的是 [ys[i], ys[i+1])。· 事件处理顺序:先计算面积,再处理当前 x 的事件,避免将当前 x 上的矩形误计入上一段间隔。该方法可
·
下面是 LeetCode 850 题「矩形面积 II」的 Java 解法。该解法使用扫描线 + 离散化 + 线段树,时间复杂度 O(n log n),空间复杂度 O(n)。
import java.util.*;
class Solution {
private static final int MOD = 1_000_000_007;
public int rectangleArea(int[][] rectangles) {
// 1. 收集所有 y 坐标,用于离散化
List<Integer> yList = new ArrayList<>();
for (int[] rect : rectangles) {
yList.add(rect[1]);
yList.add(rect[3]);
}
Collections.sort(yList);
// 去重
List<Integer> ys = new ArrayList<>();
for (int i = 0; i < yList.size(); i++) {
if (i == 0 || !yList.get(i).equals(yList.get(i - 1))) {
ys.add(yList.get(i));
}
}
int m = ys.size();
// 离散化映射:y 值 -> 下标
Map<Integer, Integer> yToIdx = new HashMap<>();
for (int i = 0; i < m; i++) {
yToIdx.put(ys.get(i), i);
}
// 2. 构建事件
List<Event> events = new ArrayList<>();
for (int[] rect : rectangles) {
int x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3];
// 左边界:+1,右边界:-1
events.add(new Event(x1, y1, y2, 1));
events.add(new Event(x2, y1, y2, -1));
}
// 按 x 排序
events.sort(Comparator.comparingInt(e -> e.x));
// 3. 线段树初始化,区间个数 = m-1
SegTree segTree = new SegTree(ys);
// 4. 扫描线
long ans = 0;
int lastX = events.get(0).x;
int i = 0;
int n = events.size();
while (i < n) {
int curX = events.get(i).x;
// 计算从上个 x 到当前 x 的面积
ans = (ans + (long) (curX - lastX) * segTree.queryTotal()) % MOD;
lastX = curX;
// 处理所有 x == curX 的事件
while (i < n && events.get(i).x == curX) {
Event e = events.get(i);
int y1Idx = yToIdx.get(e.y1);
int y2Idx = yToIdx.get(e.y2);
// 更新区间 [y1Idx, y2Idx-1] 对应的线段树
segTree.update(y1Idx, y2Idx - 1, e.type);
i++;
}
}
return (int) ans;
}
// 事件类
class Event {
int x, y1, y2, type; // type = 1 添加,-1 删除
Event(int x, int y1, int y2, int type) {
this.x = x;
this.y1 = y1;
this.y2 = y2;
this.type = type;
}
}
// 线段树,维护区间覆盖次数和覆盖总长度
class SegTree {
private int[] cover; // 覆盖次数
private long[] length; // 当前覆盖长度(实际长度)
private List<Integer> ys; // 离散化后的 y 坐标列表
public SegTree(List<Integer> ys) {
this.ys = ys;
int size = ys.size() - 1; // 区间个数
cover = new int[4 * size];
length = new long[4 * size];
}
// 更新区间 [l, r] 增加 val(val = 1 或 -1)
public void update(int l, int r, int val) {
update(1, 0, ys.size() - 2, l, r, val);
}
private void update(int node, int nodeL, int nodeR, int l, int r, int val) {
if (l <= nodeL && nodeR <= r) {
cover[node] += val;
// 根据覆盖情况重新计算当前节点的长度
if (cover[node] > 0) {
// 完全覆盖,长度就是区间对应的实际长度
length[node] = ys.get(nodeR + 1) - ys.get(nodeL);
} else {
// 没有覆盖,长度等于子节点长度之和(如果是叶子则为 0)
if (nodeL == nodeR) {
length[node] = 0;
} else {
length[node] = length[node * 2] + length[node * 2 + 1];
}
}
return;
}
int mid = (nodeL + nodeR) / 2;
if (l <= mid) update(node * 2, nodeL, mid, l, r, val);
if (r > mid) update(node * 2 + 1, mid + 1, nodeR, l, r, val);
// 回溯时更新当前节点长度(前提是 cover[node] == 0)
if (cover[node] == 0) {
length[node] = length[node * 2] + length[node * 2 + 1];
}
}
// 查询当前被覆盖的总长度
public long queryTotal() {
return length[1];
}
}
}
核心思路
- 离散化 y 坐标:由于 y 的范围很大(10^9),无法直接开数组,因此将所有矩形的 y1、y2 收集起来排序去重,得到 m 个不同的 y 值,形成 m-1 个小区间。
- 扫描线:将每个矩形拆成左边界(+1)和右边界(-1)事件,按 x 坐标排序。
- 线段树维护 y 轴覆盖长度:
· 线段树的叶子节点对应一个小区间 [ys[i], ys[i+1]),节点存储当前被覆盖的次数(cover)和该区间内被覆盖的实际长度(len)。
· 当某个区间的 cover > 0 时,len 等于区间实际长度;否则 len 等于左右子节点 len 之和。
· 更新操作为区间加 1 或减 1,递归到完全覆盖的节点后直接修改 cover 并更新 len,无需下推懒标记(因为 cover 本身就是标记)。 - 面积累加:扫描时,每到一个新的 x,先计算上一个 x 到当前 x 之间的面积 = 当前覆盖总长度 × x 间隔,然后处理该 x 上的所有事件(更新线段树)。最后将结果模 10^9+7 返回。
注意点
· 事件处理顺序:先计算面积,再处理当前 x 的事件,避免将当前 x 上的矩形误计入上一段间隔。
· 线段树更新时,区间为 [y1Idx, y2Idx-1],因为离散化下标 i 对应的是 [ys[i], ys[i+1])。
· 使用 long 类型防止中间结果溢出,最后取模。
该方法可以高效处理最多 200 个矩形(题目数据范围),是官方题解的标准做法。
更多推荐




所有评论(0)