【LeetCode每日一题】【2023/1/2】1801. 积压订单中的订单总数

news/2024/5/20 6:20:54 标签: leetcode, 算法, c++, , 优先队列

文章目录

  • 1801. 积压订单中的订单总数
    • 方法1:模拟+优先队列
      • part1
      • priority_queue的使用
      • part2
      • 求余
      • 代码


1801. 积压订单中的订单总数

LeetCode: 1801. 积压订单中的订单总数

中等 \color{#FFB800}{中等} 中等

给你一个二维整数数组 orders ,其中每个 orders[i] = [price_i, amount_i, orderType_i] 表示有
amount_i 笔类型为 orderType_i 、价格为 price_i 的订单。

订单类型 orderType_i 可以分为两种:

  • 0 表示这是一批采购订单 buy
  • 1 表示这是一批销售订单 sell

注意,orders[i] 表示一批共计 amount_i 笔的独立订单,这些订单的价格和类型相同。对于所有有效的 i ,由 orders[i] 表示的所有订单提交时间均早于 orders[i+1] 表示的所有订单。

存在由未执行订单组成的 积压订单 。积压订单最初是空的。提交订单时,会发生以下情况:

  • 如果该订单是一笔采购订单 buy ,则可以查看积压订单中价格 最低 的销售订单 sell 。如果该销售订单 sell 的价格 低于或等于 当前采购订单 buy 的价格,则匹配并执行这两笔订单,并将销售订单 sell 从积压订单中删除。否则,采购订单 buy 将会添加到积压订单中。
  • 反之亦然,如果该订单是一笔销售订单 sell ,则可以查看积压订单中价格 最高 的采购订单 buy 。如果该采购订单 buy 的价格 高于或等于 当前销售订单 sell 的价格,则匹配并执行这两笔订单,并将采购订单 buy 从积压订单中删除。否则,销售订单 sell 将会添加到积压订单中。

输入所有订单后,返回积压订单中的 订单总数 。由于数字可能很大,所以需要返回对 10^9 + 7 取余的结果。

示例 1:
在这里插入图片描述

输入:orders = [[10,5,0],[15,2,1],[25,1,1],[30,4,0]]
输出:6
解释:输入订单后会发生下述情况:
- 提交 5 笔采购订单,价格为 10 。没有销售订单,所以这 5 笔订单添加到积压订单中。
- 提交 2 笔销售订单,价格为 15 。没有采购订单的价格大于或等于 15 ,所以这 2 笔订单添加到积压订单中。
- 提交 1 笔销售订单,价格为 25 。没有采购订单的价格大于或等于 25 ,所以这 1 笔订单添加到积压订单中。
- 提交 4 笔采购订单,价格为 30 。前 2 笔采购订单与价格最低(价格为 15)的 2 笔销售订单匹配,从积压订单中删除这 2 笔销售订单。第 3 笔采购订单与价格最低的 1 笔销售订单匹配,销售订单价格为 25 ,从积压订单中删除这 1 笔销售订单。积压订单中不存在更多销售订单,所以第 4 笔采购订单需要添加到积压订单中。
最终,积压订单中有 5 笔价格为 10 的采购订单,和 1 笔价格为 30 的采购订单。所以积压订单中的订单总数为 6

示例 2:
在这里插入图片描述

输入:orders = [[7,1000000000,1],[15,3,0],[5,999999995,0],[5,1,1]]
输出:999999984
解释:输入订单后会发生下述情况:
- 提交 109 笔销售订单,价格为 7 。没有采购订单,所以这 109 笔订单添加到积压订单中。
- 提交 3 笔采购订单,价格为 15 。这些采购订单与价格最低(价格为 7 )的 3 笔销售订单匹配,从积压订单中删除这 3 笔销售订单。
- 提交 999999995 笔采购订单,价格为 5 。销售订单的最低价为 7 ,所以这 999999995 笔订单添加到积压订单中。
- 提交 1 笔销售订单,价格为 5 。这笔销售订单与价格最高(价格为 5 )的 1 笔采购订单匹配,从积压订单中删除这 1 笔采购订单。
最终,积压订单中有 (1000000000-3) 笔价格为 7 的销售订单,和 (999999995-1) 笔价格为 5 的采购订单。所以积压订单中的订单总数为 1999999991 ,等于 999999984 % (109 + 7)

提示:

  • 1 <= orders.length <= 10^5
  • orders[i].length == 3
  • 1 <= price_i, amount_i <= 10^9
  • orderType_i01

方法1:模拟+优先队列

part1

按题意:如果是 buy 订单,我们要寻找 价格 最低的 sell 订单进行操作;如果是 sell 订单,我们要寻找 价格 最高的 buy 订单进行操作。若满足相应的条件,则可以视为当前订单与目标订单 “抵消” 。

所以我们需要对两种订单进行排序。由于题目要求订单 order[i] 的出现时间比 order[i+1] 早,因此我们需要在算法中,按序处理订单。这里就需要用到 模拟 的方法。同时,sell 序列和 buy 序列中的元素会随着订单的出现和变化,因此我们选择使用 优先队列)来排序。

对于 模拟 的过程,我们只需要创建一个循环,在循环中遍历订单即可。对于 排序 ,要寻找 价格 最低的 sell 订单则 sellBacking 是一个 小根 ;要寻找 价格 最高的 buy 订单则 buyBacking 是一个 大根 。C++的标准库中,在头文件 queue 中给出了优先队列)这个数据结构 priority_queue 。其默认为大根

有了数据结构之后,我们应思考这个里面元素的类型。我们首先也许会将 价格 作为一个整型存放到里面,毕竟 按价格排序 。但是题目中给出要求 1 <= amount_i <= 10^9 ,也就是说一批订单 order[i] 内最高可达到 10的9次方 笔订单。这就意味这我们会在中压入 109 个整型,又要循环 109 次来处理 一批 订单。这就一来,一批 订单就要需要消耗 109 * sizeof(int) 约为 3.7GB 内存空间,更何况订单最多有 105 批(1 <= orders.length <= 10^5)。所以这个简单的想法是不现实的。

我们要把 价格数量 组合为一个二元组 std::pair 存入中,即的元素类型为 std::pair<int, int> (题目给出的函数定义中,价格和数量即为 int )。


priority_queue的使用

优先队列,或者说小根、大根,若不使用 priority_queue 实现,可以跳过)

在C++中,我们在定义 优先队列 变量的时候,若 元素类型复杂元素间比较方式需自定义,则需要给出排序的函数,告诉这个怎么去进行两个元素的比较从而确定它们的先后关系。因为我们只需要对 价格 进行排序,而 数量 不作为排序的依据。如 sell 订单的 比较函数 如下:

// pair<price, amount>
auto sellCompare = [](
    const std::pair<int, int> &left,
    const std::pair<int, int> &right
)
{ return left.first > right.first; };

优先队列对元素进行排序的时候,每次会拿出两个元素,调用这个函数来确定先后关系。同时,上述代码在定义 二元组 的时候,将 价格 放在了二元组的第一个元素处,因此我们实现了对 leftrightfirst 成员进行比较。

在cppreference上,我们可以查看 priority_queue 的详细内容。有句话是这么告诉我们的:

可用用户提供的 Compare 更改顺序,例如,用 std::greater 将导致最小元素作为 top() 出现。

Compare 即为我们提供的 比较函数 ,例如上面代码里的 sellCompare 函数。std::greater 可以理解为这个函数的比较方式是: “左边”的元素 left “大于” “右边”的元素 right,例如上面代码里的返回语句 left.first > right.first。当 “左边大于右边” 这个条件成立(比较函数 返回 true)时,此时这个是一个 小根

这样一来,我们提供了 sellBacking 这个的比较函数,通过这个比较函数,使得了这个成为了 小根。同时我们也告诉了这个怎么比较元素,那就是比较两边的 first

接下来我们就正式去定以两个变量 sellBackingbuyBacking 。在cppreference上给出了 priority_queue 的模板参数:

template<
    class T,
    class Container = std::vector<T>,
    class Compare = std::less<typename Container::value_type>
> class priority_queue;

首先,第一个模板参数是这个优先队列的元素类型 T ,在本题中即为二元组 std::pair<int, int>

如果元素类型 T 是一般的类型,比如说 floatint 之类,那么后面两个参数就不用写明。但是我们这里的元素类型是一个比较复杂的 二元组 ,并且元素的比较方式也是自己定义(比较 first 成员)。这两个因素都导致我们需要提供自己的比较函数,继而需要写明后面两个模板参数。第二个参数是用来存放这些元素的容器类型,我们可以使用 std::vector 数组,写为 std::vector<std::pair<int, int> > 。第三个参数是比较函数它本身的类型,我们可以用 decltype 函数得到这个类型,写为 decltype(sellCompare)

最后,在构造函数中传入这个比较函数作为参数即可。

关于小根 sellBacking 和大根 buyBacking 的定义如下:

// pair<price, amount>
typedef std::pair<int, int> Record;

auto sellCompare = [](const Record &left, const Record &right)
{ return left.first > right.first; };
priority_queue<Record, vector<Record>, decltype(sellCompare)> sellBacking{sellCompare};

auto buyCompare = [](const Record &left, const Record &right)
{ return left.first < right.first; };
priority_queue<Record, vector<Record>, decltype(buyCompare)> buyBacking{buyCompare};
  • 通过比较函数 sellCompare左边“大于”右边 的写法,使得 sellBacking 为小根
  • 通过比较函数 buyCompare左边“小于”右边 的写法,使得 buyBacking 为大根

part2

sellBacking 为小根buyBacking 为大根

buy 订单为例,每次接受到一批存放在 order[i] 里的 buy 订单,我们要去访问 sellBacking顶。接下来会有以下情况出现:

  1. 不满足条件(sell 的价格 低于或等于 当前采购订单 buy 的价格),将 buy 订单压入 buyBacking
    • 满足条件,但 buy 订单 不够 抵消完 顶的 sell 订单。此时需要更改 sellBacking 顶元素,将 sell 订单的数量减去已经抵消的数量。(priority_queue 不能直接修改顶,需要弹出、修改、再压入)。
    • buy 订单 足够 抵消完 顶的 sell 订单。此时需要弹出顶。若 buy 订单还有剩余,那么还需要对弹出后的 进行 再次匹配
  2. 无论如何到了最后,若 buy 订单的数量依旧有剩余,则压入 buyBacking

同理,sell 订单的处理过程与上述基本相同。


求余

对于数 abmod(a + b) % mod == (a % mod + b % mod) % mod

即 “和的余数” 等于 “余数的和再求余”


代码

最后的代码如下:

#include <vector>
#include <queue>
using namespace std;

class Solution
{
public:
    // pair<price, amount>
    typedef std::pair<int, int> Record;

    int getNumberOfBacklogOrders(vector<vector<int>> &orders)
    {

        auto sellCompare = [](const Record &left, const Record &right)
        { return left.first > right.first; };
        priority_queue<Record, vector<Record>, decltype(sellCompare)> sellBacking{sellCompare};

        auto buyCompare = [](const Record &left, const Record &right)
        { return left.first < right.first; };
        priority_queue<Record, vector<Record>, decltype(buyCompare)> buyBacking{buyCompare};

        for (const vector<int> &order : orders)
        {
            const int &price = order[0];
            int amount = order[1];
            const int &orderType = order[2];

            if (orderType == 0)
            { // buy order
                int sellPrice = 0, sellAmount = 0;

                while (!sellBacking.empty() && sellBacking.top().first <= price && amount > 0)
                {
                    sellPrice = sellBacking.top().first;
                    sellAmount = sellBacking.top().second;
                    sellBacking.pop();
                    amount -= sellAmount;
                }

                if (amount > 0)
                {
                    buyBacking.emplace(price, amount);
                }
                else
                {
                    sellAmount = std::abs(amount);
                    sellBacking.emplace(sellPrice, sellAmount);
                }
            }
            else
            { // sell order
                int buyPrice = 0, buyAmount = 0;

                while (!buyBacking.empty() && buyBacking.top().first >= price && amount > 0)
                {
                    buyPrice = buyBacking.top().first;
                    buyAmount = buyBacking.top().second;
                    buyBacking.pop();
                    amount -= buyAmount;
                }

                if (amount > 0)
                {
                    sellBacking.emplace(price, amount);
                }
                else
                {
                    buyAmount = std::abs(amount);
                    buyBacking.emplace(buyPrice, buyAmount);
                }
            }
        }

        constexpr int mod = 1000000007;
        int ans = 0;
        for (; !sellBacking.empty(); sellBacking.pop())
            ans = (ans + sellBacking.top().second) % mod;
        for (; !buyBacking.empty(); buyBacking.pop())
            ans = (ans + buyBacking.top().second) % mod;
        return ans;
    }
};

复杂度分析:

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)。其中,n 为数组 orders 的长度。首先需要遍历 orders 数组($O(n));其次在遍历过程中,对优先队列)的每次压入、弹出操作均为 O ( l o g n ) O(logn) O(logn)。总的来说,时间复杂度为两者乘积 O ( n l o g n ) O(nlogn) O(nlogn)

  • 空间复杂度: O ( n ) O(n) O(n)优先队列需要的 O ( n ) O(n) O(n)的空间。

参考结果

Accepted
69/69 cases passed (188 ms)
Your runtime beats 87.63 % of cpp submissions
Your memory usage beats 55.67 % of cpp submissions (5.9 MB)

http://www.niftyadmin.cn/n/23629.html

相关文章

国内有没有可以全职远程办公的程序员工作?

明作为一个曾经靠兼职开发远程办公来赚钱的程序员&#xff0c;既碰到过无良甲方&#xff0c;开发完了不结尾款&#xff0c;最后通过法律手段才解决问题&#xff1b;也接过自称甲方的中介单&#xff0c;耗费心力拿到尾款&#xff0c;最后发现人家拿的钱比自己还多......这一路兼…

计算机基础知识(基础入门小白专属)九

♥️作者&#xff1a;小刘在这里 ♥️每天分享云计算网络运维课堂笔记&#xff0c;疫情之下&#xff0c;你我素未谋面&#xff0c;但你一定要平平安安&#xff0c;一 起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放&#xff0c;…

性能优化系列之如何选择合适的模块化加载方案?

文章の目录一、JavaScript 模块化加载方案和选型1、CommonJS2、AMD (Asynchronous Module Definition)&#xff08;异步模块定义&#xff09;规范3、CMD&#xff08; Common Module Definition&#xff09;&#xff08;通用模块定义&#xff09;规范4、ES6 import写在最后一、J…

常用设计模式-代理模式

代理模式给某一个对象提供一个代理对象&#xff0c;并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介 代理对象与目标对象要实现相同的接口 优点&#xff1a; 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用&#xff1b; …

1-计算机系统概述

文章目录一.操作系统的基本概念&#xff08;一&#xff09;操作系统的特征&#xff08;二&#xff09;操作系统的目标和功能二.操作系统的发展与分类&#xff08;一&#xff09;手工操作阶段&#xff08;二&#xff09;批处理阶段&#xff08;三&#xff09;分时操作系统&#…

环境变量?拿来把你!

文章目录环境变量直接运行程序的第一种方法&#xff1a;把程序移动到系统目录底下echo $环境变量&#xff1a;查看环境变量PATH:指定命令的搜索路径export 定义一个新的环境变量export PATH旧路径&#xff1a;新路径getenv&#xff1a;获取环境变量—获取环境变量的第一种方式s…

Vert.x 核心概念及事件模型

Vert.x是基于事件的&#xff0c;提供一个事件驱动编程模型 使用Vert.x作为服务器时&#xff0c;程序员只要编写事件处理器event handler即可。&#xff08;当TCP socket有数据时&#xff0c;event handler被创建调用&#xff09; 另外它还可以在以下几种情况激活&#xff1a; …

10四个基本子空间

四个基本子空间 四个基本空间介绍 对于一个 m*n 矩阵 A 来说&#xff0c;以下四个基本空间是其基础。 2.1 四个基本空间的维数与基 还是研究 m*n 的矩阵 A&#xff0c;其四个子空间的基本性质如下: &#xff08;1&#xff09;列空间 之前介绍过列空间的基&#xff0c;设矩…