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


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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亡心灵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值