• 850. 矩形面积 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),三个矩形都重叠。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    示例 2:

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

    提示:

    1 <= rectangles.length <= 200
    rectanges[i].length = 4
    0 <= xi1, yi1, xi2, yi2 <= 109
    矩形叠加覆盖后的总面积不会超越 2^63 - 1 ,这意味着可以用一个 64 位有符号整数来保存面积结果。

    离散化 + 扫描线 + 使用简单数组实时维护

    这是一道「扫描线」模板题。

    将所有给定的矩形的左右边对应的 x 端点提取出来并排序,每个端点可看作是一条竖直的线段(红色),问题转换为求解「由多条竖直线段分割开」的多个矩形的面积总和(黄色):

    在这里插入图片描述
    相邻线段之间的宽度为单个矩形的「宽度」,问题转换为求该区间内高度的并集(即矩形的高度)。

    我们可以对给定的所有矩形进行遍历,统计所有对该矩形有贡献的 y 值线段,再对线段进行求交集(总长度),即可计算出该矩形的「高度」,从而计算出来该矩形的面积。

    思路与算法

    我们先解释扫描线的概念:想象一条竖直的直线从平面的最左端扫到最右端,在扫描的过程中,直线上的一些线段会被给定的矩形覆盖。将这些覆盖的线段长度进行积分,就可以得到矩形的面积之和。每个矩形有一个左边界和一个右边界,在扫描到矩形的左边界时,覆盖的长度可能会增加;在扫描到矩形的右边界时,覆盖的长度可能会减少。如果给定了 n 个矩形,那么覆盖的线段长度最多变化 2n 次,此时我们就可以将两次变化之间的部分合并起来,一起计算:即这一部分矩形的面积,等于覆盖的线段长度,乘以扫描线在水平方向移动过的距离。

    因此,我们可以首先将所有矩形的左右边界按照横坐标进行排序,这样就确定了扫描线扫描的顺序。随后我们遍历这些左右边界,一次性地处理掉一批横坐标相同的左右边界,对应地增加或者减少覆盖的长度。在这之后,下一个未遍历到的坐右边界的横坐标,减去这一批左右边界的横坐标,就是扫描线在水平方向移动过的距离。

    那么我们如何维护「覆盖的线段长度」呢?这里同样可以使用到离散化的技巧(扫描线就是一种离散化的技巧,将大范围的连续的坐标转化成 2n 个离散的坐标)。由于矩形的上下边界也只有 2n 个,它们会将 y 轴分成 2n+1 个部分,中间的 2n-1 个部分均为线段,会被矩形覆盖到(最外侧的 2 个部分为射线,不会被矩形覆盖到),并且每一个线段要么完全被覆盖,要么完全不被覆盖。因此我们可以使用两个长度为 2n-1 的数组 seg 和 length,其中 seg[i] 表示第 i 个线段被矩形覆盖的次数length[i] 表示第 i 个线段的长度。当扫描线遇到一个左边界时,我们就将左边界覆盖到的线段对应的seg[i] 全部加 1;遇到一个右边界时,我们就将右边界覆盖到的线段对应的 seg[i] 全部减 1。在处理掉一批横坐标相同的左右边界后,seg[i] 如果大于 0,说明它被覆盖,我们累加所有的 length[i],即可得到「覆盖的线段长度」。

    class Solution {
    public:
        int rectangleArea(vector<vector<int>>& rectangles) {
            int n = rectangles.size();
            vector<int> hbound;
            for (const auto& rect: rectangles) {
                // 下边界
                hbound.push_back(rect[1]);
                // 上边界
                hbound.push_back(rect[3]);
            }
            sort(hbound.begin(), hbound.end());
            hbound.erase(unique(hbound.begin(), hbound.end()), hbound.end());
            int m = hbound.size();
            // 「思路与算法部分」的 length 数组并不需要显式地存储下来
            // length[i] 可以通过 hbound[i+1] - hbound[i] 得到
            vector<int> seg(m - 1);
    
            vector<tuple<int, int, int>> sweep;
            for (int i = 0; i < n; ++i) {
                // 左边界
                sweep.emplace_back(rectangles[i][0], i, 1);
                // 右边界
                sweep.emplace_back(rectangles[i][2], i, -1);
            }
            sort(sweep.begin(), sweep.end());
    
            long long ans = 0;
            for (int i = 0; i < sweep.size(); ++i) {
                int j = i;
                while (j + 1 < sweep.size() && get<0>(sweep[i]) == get<0>(sweep[j + 1])) {
                    ++j;
                }
                if (j + 1 == sweep.size()) {
                    break;
                }
                // 一次性地处理掉一批横坐标相同的左右边界
                for (int k = i; k <= j; ++k) {
                    auto&& [_, idx, diff] = sweep[k];
                    int left = rectangles[idx][1], right = rectangles[idx][3];
                    for (int x = 0; x < m - 1; ++x) {
                        if (left <= hbound[x] && hbound[x + 1] <= right) {
                            seg[x] += diff;
                        }
                    }
                }
                int cover = 0;
                for (int k = 0; k < m - 1; ++k) {
                    if (seg[k] > 0) {
                        cover += (hbound[k + 1] - hbound[k]);
                    }
                }
                ans += static_cast<long long>(cover) * (get<0>(sweep[j + 1]) - get<0>(sweep[j]));
                i = j;
            }
            return ans % static_cast<int>(1e9 + 7);
        }
    };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    在这里插入图片描述

  • 相关阅读:
    Win11怎么彻底关闭粘滞键功能
    linux内存分配器
    Linux的root用户
    [免费专栏] Android安全之动态代码注入技术(利用JDB调试APK)
    探索H5互动广告:创新数字营销的未来
    Linux服务器初始化、yum安装java、redis、mysql
    使用libevent实现回显服务器
    数组的左旋和右旋算法
    一个UWP 框架开发的哔哩哔哩非官方应用
    基于Python的热门音乐特征数据分析
  • 原文地址:https://blog.csdn.net/qq_40713201/article/details/126892415