leetcode
leetcode 751 ~ 800
矩形面积 II

矩形面积 II

难度:

标签:

题目描述

给你一个轴对齐的二维数组 rectangles 。 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐标, (xi1, yi1) 是该矩形 左下角 的坐标, (xi2, yi2) 是该矩形 右上角 的坐标。

计算平面中所有 rectangles 所覆盖的 总面积 。任何被两个或多个矩形覆盖的区域应只计算 一次

返回 总面积 。因为答案可能太大,返回 109 + 7 的  。

 

示例 1:

输入:rectangles = [[0,0,2,2],[1,0,2,3],[1,0,3,1]]
输出:6
解释:如图所示,三个矩形覆盖了总面积为 6 的区域。
从(1,1)到(2,2),绿色矩形和红色矩形重叠。
从(1,0)到(2,3),三个矩形都重叠。

示例 2:

输入:rectangles = [[0,0,1000000000,1000000000]]
输出:49
解释:答案是 1018 对 (109 + 7) 取模的结果, 即 49 。

 

提示:

  • 1 <= rectangles.length <= 200
  • rectanges[i].length = 4
  • 0 <= xi1, yi1, xi2, yi2 <= 109

代码结果

运行时间: 36 ms, 内存: 16.3 MB


/*
  思路:
  1. 使用流处理来简化集合操作和排序。
  2. 保持与传统Java代码相同的逻辑,主要使用流来管理事件和Y坐标集合。
  3. 使用扫描线算法计算覆盖面积。
*/

import java.util.*;
import java.util.stream.Collectors;

public class RectangleAreaStream {
    private static final int MOD = 1000000007;

    public int rectangleArea(int[][] rectangles) {
        List<int[]> events = Arrays.stream(rectangles)
                .flatMap(rect -> Stream.of(
                        new int[]{rect[0], rect[1], rect[3], 1}, // left edge
                        new int[]{rect[2], rect[1], rect[3], -1} // right edge
                ))
                .sorted(Comparator.comparingInt(a -> a[0]))
                .collect(Collectors.toList());

        List<Integer> yList = Arrays.stream(rectangles)
                .flatMapToInt(rect -> IntStream.of(rect[1], rect[3]))
                .distinct()
                .sorted()
                .boxed()
                .collect(Collectors.toList());

        int[] active = new int[yList.size()];
        long prevX = 0;
        long area = 0;
        for (int[] event : events) {
            int x = event[0], y1 = event[1], y2 = event[2], type = event[3];
            long yCover = 0;
            for (int j = 0; j < yList.size() - 1; ++j) {
                if (active[j] > 0) {
                    yCover += yList.get(j + 1) - yList.get(j);
                }
            }
            area = (area + yCover * (x - prevX)) % MOD;
            prevX = x;
            for (int j = 0; j < yList.size(); ++j) {
                if (y1 <= yList.get(j) && yList.get(j) < y2) {
                    active[j] += type;
                }
            }
        }
        return (int) area;
    }
}

解释

方法:

该题解的思路是利用扫描线算法。首先将所有矩形的左右边界x坐标去重并排序,建立x坐标到索引的映射。然后遍历每个矩形,将其在x方向上覆盖的区间内的每个位置记录下该矩形在y方向上的区间。接着对于x方向每个分割后的小区间,将其包含的y区间按起点排序,然后用类似合并区间的方法计算出y方向覆盖的长度。最后将所有小矩形区域的面积累加起来即可得到最终结果。

时间复杂度:

O(n^2logn)

空间复杂度:

O(n^2)

代码细节讲解

🦆
扫描线算法中,如何处理重叠的矩形以确保每个区域只被计算一次?
在扫描线算法中,处理重叠的矩形的关键在于合并重叠的y区间。算法首先将每个x区间内的所有y区间收集起来,然后按照y区间的起点进行排序。通过遍历排序后的y区间列表,若当前区间与前一个区间重叠(即当前区间的起点小于或等于前一个区间的终点),则将这两个区间合并为一个新区间,其终点为两个区间终点的最大值。这样,每个x区间内的y区间都被合并为几个不重叠的区间,确保每个具体的y区间只计算一次面积。
🦆
为什么需要将所有x坐标去重并排序?去重和排序对算法的影响是什么?
去重和排序x坐标是为了确定x方向上的区间划分。首先,去重是必要的,因为重复的x坐标对于划分区间没有帮助,只会增加不必要的计算量。其次,排序后的x坐标可以帮助我们确定每一个小的x区间(即两个相邻的x坐标之间的区间),这些区间是计算面积分割的基础。在已排序的x坐标基础上,我们可以确保遍历这些x区间时,是按照从左到右的顺序处理,从而正确地计算覆盖在每个x区间上的y区间的面积。
🦆
代码中的`cnt`数组是如何使用的?它在算法中扮演了什么角色?
`cnt`数组是用来存储每个x区间对应的所有y区间的集合。在遍历输入的矩形时,算法会将每个矩形的y区间根据其x边界映射到对应的`cnt`数组的元素中。这样,`cnt[i]`包含了所有在第i个x区间(由相邻的x坐标定义)内的y区间。这个数组的存在使得算法可以对单个x区间内的所有y区间进行处理,如合并重叠区间,并计算该x区间的总y覆盖长度,这是计算总面积的关键步骤。
🦆
为什么在处理y区间时需要先对其按起点进行排序?
对y区间按起点进行排序是为了简化合并重叠区间的过程。排序后的y区间列表允许算法逐一检查每个区间,并与前一个合并后的区间进行比较,判断是否需要合并。如果列表不经排序,算法在处理每个新的y区间时,可能需要回溯到之前的区间进行比较,这会复杂化合并过程并增加算法的时间复杂度。排序确保了处理的顺序性和简洁性,使得合并重叠区间的操作更高效、直观。

相关问题