LeetCode | 850. 矩形面积 II

news2024/11/26 20:33:47

我们给出了一个(轴对齐的)二维矩形列表 rectangles 。 对于 rectangle[i] = [xi1, yi1, xi2, yi2], 表示第 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
解释:答案是 10^18 对 (10^9 + 7) 取模的结果, 即 49 。

提示:

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

方案一:O(n2logn)的时间复杂度

  1. 获取所有矩形的两条竖边的 x 坐标,按 x 从小到大排序。
  2. 遍历 x 坐标,值相同的跳过(可以在第一步做一个去重), 找到两个不同的坐标 x1 , x2。
  3. 遍历所有的矩形,记录下横坐标区间覆盖了 x1, x2 的矩形的两个 y 坐标。
  4. 统计出这些 y 坐标的总长度(高度),乘以 x2 - x1 (宽度),就是(x1, x2) 区间中矩形的面积
  5. 继续遍历,遍历完所有的区间,就是所有矩形的总面积

例:
在这里插入图片描述
遍历到 (x1, x2) 区间时, 有 4 个矩形覆盖了这个区间:R1、R2、R3、R4;统计出来的高度为:H = (y2 - y1) + (y6 - y3) + (y8 - y7)。面积为: H * (x2 - x1)。(R2 和 R3 重复的面积没有算,统计出来的高度是 y6 - y3, 而不是 y6 - y4 + y5 - y3)
上一个遍历的区间是前一个 x 到 x1, 下一个要遍历的区间是 (x2, x3)。因为 x 坐标是根据矩形的竖边确定的,所以不会错过任何矩形的面积漏算,一定会统计出所有的面积。

int rectangleArea(vector<vector<int>>& rectangles) {
    const int MOD = 1e9 + 7;
    vector<int> vecX;
    for (auto &rect : rectangles) {
        vecX.push_back(rect[0]);
        vecX.push_back(rect[2]);
    }
    sort(vecX.begin(), vecX.end());

    long ans = 0;
    for (int i = 1; i < vecX.size(); ++i) {
        int a = vecX[i - 1], b = vecX[i];
        if (a == b) 
            continue;
        vector<vector<int>> lines;
        for (auto &rect : rectangles) {
            if (rect[0] <= a && b <= rect[2]) { // 矩形覆盖当前区间(矩形在区间内),将矩形的y坐标记录下来
                lines.push_back({rect[1], rect[3]});
            }
        }
        sort(lines.begin(), lines.end(), [](vector<int>& a, vector<int>& b) -> bool {
            return a[0] < b[0] || (a[0] == b[0] && a[1] < b[1]);
        });
        
        long height = 0, prey1 = -1, prey2 = -1;
        for (auto &cur : lines) {
            if (cur[0] > prey2) { // 和上一个纵区间不相交
                height += prey2 - prey1;
                prey1 = cur[0];
                prey2 = cur[1];
            } else if (cur[1] > prey2) { // 和上一个纵区间相交,合并成一个
                prey2 = cur[1];
            }
        }
        height += prey2 - prey1;
        ans += height * (b - a);
        ans %= MOD;
    }
    return ans;
}

方案二:O(n2) 的时间复杂度

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()); // 删除重复的 y 坐标
    int m = hbound.size();

    vector<int> seg(m - 1);

    vector<tuple<int, int, int>> sweep; // 分别表示:x 坐标,第几个矩形,左边界还是右边界
    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 y1 = rectangles[idx][1], y2 = rectangles[idx][3];
            for (int x = 0; x < m - 1; ++x) {
                if (y1 <= hbound[x] && hbound[x + 1] <= y2) { // 纵坐标包含了
                    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);
}

方案三: O(nlogn)的时间复杂度

    struct Segtree {
        int cover;
        int length;
        int max_length;
    };
    int rectangleArea(vector<vector<int>>& rectangles) {
        int n = rectangles.size();
        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();
        // 线段树有 m-1 个叶子节点,对应着 m-1 个会被完整覆盖的线段,需要开辟 ~4m 大小的空间
        tree.resize(m * 4 + 1);
        init(1, 1, 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 = lower_bound(hbound.begin(), hbound.end(), rectangles[idx][1]) - hbound.begin() + 1;
                int right = lower_bound(hbound.begin(), hbound.end(), rectangles[idx][3]) - hbound.begin();
                update(1, 1, m - 1, left, right, diff);
            }
            ans += static_cast<long long>(tree[1].length) * (get<0>(sweep[j + 1]) - get<0>(sweep[j]));
            i = j;
        }
        return ans % static_cast<int>(1e9 + 7);
    }

    void init(int idx, int l, int r) {
        tree[idx].cover = tree[idx].length = 0;
        if (l == r) {
            tree[idx].max_length = hbound[l] - hbound[l - 1];
            return;
        }
        int mid = (l + r) / 2;
        init(idx * 2, l, mid);
        init(idx * 2 + 1, mid + 1, r);
        tree[idx].max_length = tree[idx * 2].max_length + tree[idx * 2 + 1].max_length;
    }

    void update(int idx, int l, int r, int ul, int ur, int diff) {
        if (l > ur || r < ul) {
            return;
        }
        if (ul <= l && r <= ur) {
            tree[idx].cover += diff;
            pushup(idx, l, r);
            return;
        }
        int mid = (l + r) / 2;
        update(idx * 2, l, mid, ul, ur, diff);
        update(idx * 2 + 1, mid + 1, r, ul, ur, diff);
        pushup(idx, l, r);
    }

    void pushup(int idx, int l, int r) {
        if (tree[idx].cover > 0) {
            tree[idx].length = tree[idx].max_length;
        }
        else if (l == r) {
            tree[idx].length = 0;
        }
        else {
            tree[idx].length = tree[idx * 2].length + tree[idx * 2 + 1].length;
        }
    }

private:
    vector<Segtree> tree;
    vector<int> hbound;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/27379.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java maven pom application 生产prod/开发dev/测试test

前言 pom 和 application.properties&#xff08;application.yml)里的定义的环境不太一样&#xff0c; pom 是maven对应的配置文件&#xff0c;编译阶段使用 application.properties&#xff08;application.yml) 是Spring配置文件&#xff0c;程序运行阶段使用 POM pom文…

每次打开百度太麻烦?用程序直接打开网页 Python实现百度划词搜索功能(获取剪切板数据)

浏览顺序实现划词功能运行错误解决问题转载声明实现划词功能 说是划词翻译&#xff0c;实际上我们是通过获取用户的剪切板内容&#xff0c;通过一系列的操作得到的。首先呢&#xff0c;我们就先实现如何获取剪切板内容的程序 首先先在桌面创建一个文件夹&#xff0c;命名为“…

pac自动代理

文章目录1.pac 定义2. 参考配置地址3. 实际配置案例3.1 编写pac文件3.1.1 return 语句有三种指令&#xff1a;3.1.2 内置函数3.1.2.1 dnsDomainIs3.1.2.2 shExpMatch3.1.2.3 isInNet3.1.2.4 主机ip3.1.2.5 dnsResolve3.1.2.6 isPlainHostName3.1.2.7 isResolvable3.1.2.8 dnsDo…

RabbitMQ初步到精通-第十章-RabbitMQ之Spring客户端源码

目录 第十章-RabbitMQ之Spring客户端源码 1. 前言 2. 客户端消费代码 2.1 消费的实现方式 2.2 消费中注解解释 2.3 推测Spring实现过程 3.MQ消费源码分析 3.1 集成SpringBoot 启动过程 3.2 Broker投递消息给客户端过程 3.3 客户端消费过程 4. 总结 第十章-RabbitMQ之S…

【Linux】线程安全

文章目录1.线程互斥1.1.线程间互斥的相关概念1.2互斥量1.3互斥量接口1.4互斥量实现原理2.可重入VS线程安全3.常见锁概念3.1死锁3.2常见死锁情况3.2.1情况一&#xff1a;忘记释放锁3.2.2情况二&#xff1a;线程重复申请锁3.2.3情况三&#xff1a;双线程多锁申请3.3锁的相关概念4…

m在VBLAST协作MIMO系统分部使用LDPC,Turbo,卷积三种信道编译码进行误码率matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB部分代码预览 4.完整MATLAB程序 1.算法描述 从上面的结构可知&#xff0c;整个卷积编码的结构可由CRC校验&#xff0c;卷积编码&#xff0c;打孔组成&#xff0c;其中打孔的作用就是讲卷积编码后的码率变为所需要的码率进行发送。 …

一种在行末隐藏有效载荷的新供应链攻击技术研判

近期&#xff0c;Phylum检测到数十个新发布的Pypi软件包执行供应链攻击&#xff0c;在这些软件包中&#xff0c;通过隐藏的__import__将窃取程序投递到开发人员的机器上。攻击者利用代码审核者所使用IDE默认的不换行代码显示设置隐藏自身的行为与载荷&#xff0c;本文将就其中出…

栈简介、手写顺序栈、手写链栈和栈的应用

一. 简介 1. 什么是栈&#xff1f; 栈是一种只能从表的一端存取数据且遵循 "先进后出"&#xff08;"后进先出"&#xff09; 原则的线性存储结构。栈也是用来存储逻辑关系为 "一对一" 数据的线性存储结构。 C#中提供顺序栈&#xff1a;Stack&…

【MySQL基础】如何安装MySQL?如何将MySQL设置成服务?

目录 一、MySQL的安装 1、解压配置 2、步骤安装 &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收藏&#x1f499;一下 一、MySQL的安装 MySQL的安装有两种方式&#xff1a;解压配置和步骤安装 1、解压配置 需提前从官网直接下载压缩包&#xff0c;进…

【MySQL篇】第二篇——库的操作

目录 创建数据库 创建数据库案例 字符集和校验规则 查看系统默认字符集以及校验规则 查看数据库支持的字符集 查看数据库支持的字符集校验规则 校验规则对数据库的影响 操纵数据库 查看数据库 显示创建语句 修改数据库 数据库删除 备份和恢复 备份 还原 注意事…

常见磁盘调度算法总结

磁盘调度算法&#x1f4d6;1. 最短寻道时间优先&#xff08;SSTF&#xff09;&#x1f4d6;2. 电梯算法&#xff08;SCAN或C-SCAN&#xff09;&#x1f4d6;3. 最短定位时间优先&#xff08;SPTF&#xff09;&#x1f4d6;4. 总结由于IO的高成本&#xff0c;操作系统在决定发送…

C语言 0 —— 计算机硬件架构及信息在计算机中的表示

当前的计算机系统&#xff0c;如Window &#xff0c;Linux&#xff0c;Mac 基本都是基于冯诺依曼的驱动架构设计的。 冯诺依曼架构输入设备先输入公式&#xff0c;给运算器&#xff0c;运算器先算 先算2*5 &#xff0c;临时放在CPU内部寄存器中&#xff0c;寄存器不够用的时候会…

vscode插件开发(四)Webview(1)

上一篇详细讲解了命令&#xff0c;这回我们一起来看一下Webview。vscode的插件其实可以分为两种&#xff0c;一种是webview插件&#xff0c;另一种是非webview插件。 webview插件的自由度很高&#xff0c;可以满足开发者的各种定制化的要求&#xff1b;而非webview插件只能使用…

我悟了!Mysql事务隔离级别其实是这样!

问题描述 ​ 最近几天在忙项目&#xff0c;有个项目是将业务收集到的数据变动&#xff0c;异步同步到一张数据表中。在测试的过程时&#xff0c;收到QA的反馈&#xff0c;说有订单的数据同步时好时坏。我怀着疑惑的表情打开了那段代码&#xff0c;它的逻辑大概是这样的&#x…

Zookeeper实现分布式锁的原理。

之前学习Redis时候&#xff0c;我们利用Redis实现了分布式锁。 黑马点评项目Redis实现分布式锁_兜兜转转m的博客-CSDN博客 为什么提出了分布式锁的概念呢&#xff1f; 因为在单体项目中&#xff0c;锁是基于JVM虚拟机实现的&#xff0c;在分布式情况下&#xff0c;JVM就不唯…

FullGC频繁,线程数持续增长排查

告警 线上应用fullgc频繁&#xff0c;收到告警 GC监控—堆内存不足 查看近12小时的监控&#xff0c;发现Survivor区一直处于 满状态、fullgc非常频繁、但没有内存溢出的现象&#xff0c;很明显是堆内存不足 GC日志分析—暂停时间并不长 因为fullgc相当频繁&#xff0c;抽…

项目管理(知识体系概述)

项目的定义:为创造独特的产品、服务或者成果进行的临时性工作。 项目的特性:1、独特的产品、服务、成果;2、临时性工作。 项目管理的目的(为了解决什么问题): 1、达成业务目标 2、满足相关方期望 3、提供项目的可预测性 4、提高项目的成果性。 5、在适当的时刻交付…

机器人运动学标定:基于考虑约束的指数积的运动学标定方法——只需要测量位置,避免冗余约束

文章目录写在前面为什么要消除归一化和正交化操作&#xff1f;只用位置而不是位姿去做标定的原因基于消除冗余约束步骤的参数辨识模型分析参考文献写在前面 基于指数积的运动学标定方法介绍&#xff1a; 机器人运动学标定&#xff1a;基于指数积的串联机构运动学标定 机器人运…

Vue表单修饰符:v-model.lazy、v-model.number、v-model.trim

表单修饰符有&#xff1a;lazy、number、trim&#xff1b;修饰符加在v-model后面&#xff1b; lazy修饰符&#xff1a; v-model的作用是双向绑定表单&#xff0c;能获取到input输入框的值&#xff0c;而且是实时获取的&#xff0c;就是当你输入框里的值发生改变就会获取到&…

【Shell 脚本速成】02、Shell 变量详解

目录 一、变量介绍 变量存取原理 二、变量定义 2.1 什么时候需要定义变量&#xff1f; 2.2 定义一个变量 定义变量举例&#xff1a; 定义变量演示&#xff1a; 2.3 取消变量 unset 2.4 有类型变量 declare declare 命令参数&#xff1a; 案例演示&#xff1a; 三…