[路飞]_前端算法第七十弹-积压订单中的订单总数

468 阅读7分钟

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

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

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

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

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

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

输入所有订单后,返回积压订单中的 订单总数 。由于数字可能很大,所以需要返回对 109 + 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) 。

这道题乍一看很唬人,让人完全看不懂的样子,咱们一起分析一下这道题。首先我们能确定,这道题需要两个存储空间,一个buy采购订单,一个sell销售订单。他们分别用orders的第三个参数orderType代表0为采购,1为销售。

orders[i] 的第一个参数为价格,第二个参数为价格相同的订单数量。

orders[i] 表示的所有订单提交时间均早于 orders[i+1] 表示的所有订单。

表示orders的订单是按顺序发放的,我们需要按顺序遍历。我们需要计算积压的订单数total,并没有说是销售单还是采购单,所以按总量计算。

如果该订单是一笔采购订单 buy ,则可以查看积压订单中价格 最低 的销售订单sell

那么说明如果该订单的type0,则需要在sell中找到最小值。

如果该订单是一笔销售订单 sell ,则可以查看积压订单中价格 最高 的采购订单 buy

那么说明如果该订单的type1,则需要在buy中找到最大值。

综上我们可以断定需要用到大小顶堆。sell为小顶堆,buy为大顶堆。

我们先创建一个堆函数:

class Heap {
  constructor(cmp = "large") {
    if (cmp == "large") {
      this.cmp = this.large;
    } else if (cmp == "small") {
      this.cmp = this.small
    } else {
      this.cmp = cmp
    }
    this.res = [];
    this.cnt = 0;
  }

  push (val) {
    this.cnt++;
    this.res.push(val)
    this.shiftUp(this.cnt - 1)
  }

  pop () {
    this.cnt--;
    const res = this.res[0]
    const pop = this.res.pop()
    if (this.cnt) {
      this.res[0] = pop
      this.shiftDown(0)
    }
    return res
  }

  shiftUp (i) {
    if (i === 0) return
    const par = this.getParentIndex(i)
    if (this.cmp(this.res[par], this.res[i])) {
      this.swap(par, i)
      this.shiftUp(par)
    }
  }

  shiftDown (i) {
    const l = this.getLeftIndex(i)
    const r = this.getRightIndex(i)
    if (l < this.cnt && this.cmp(this.res[i], this.res[l])) {
      this.swap(i, l)
      this.shiftDown(l)
    }
    if (r < this.cnt && this.cmp(this.res[i], this.res[r])) {
      this.swap(i, r)
      this.shiftDown(r)
    }
  }

  getParentIndex (i) {
    return (i - 1) >> 1
  }

  getLeftIndex (i) {
    return i * 2 + 1
  }

  getRightIndex (i) {
    return i * 2 + 2
  }

  large = (a, b) => a < b

  small = (a, b) => a > b;

  swap = (i, j) => [this.res[i], this.res[j]] = [this.res[j], this.res[i]];

  top = () => this.res[0];

  size = () => this.cnt;

  isEmpty = () => this.cnt === 0

}

我们首先遍历orders,创建大小顶堆,并添加数据

const buy = new Heap(( a,b ) => a.price < b.prcie)
const sell = new Heap(( a,b ) => a.price > b.prcie)

for(let i = 0; i<orders.length; i++){
	let [price, amount, orderType] = orders[i]
	if(orderType==0){
		buy.push({price, amount})
	}else{
		sell.push({price, amount})
	}
}

此时我们就完成了第一步,大小顶堆的创建和填值。下面我们就要进行比较了。

由上面可知,如果是销售订单,我们需要查看采购订单的堆顶。如果是采购订单,我们需要看销售订单的堆顶。

如果该销售订单 sell 的价格 低于或等于 当前采购订单 buy 的价格,则匹配并执行这两笔订单,并将销售订单 sell 从积压订单中删除。否则,采购订单 buy 将会添加到积压订单中。

也就是说,我们我发现订单是销售订单我们不能马上入堆,还需要进行比较,如果buy的堆顶大于该订单,我们需要进行抵消,同时抵消时不能完全保证两者数量相同,所以我们需要先取出buy的堆顶,进行数量抵消,剩下的那个再入堆。

else { // orderType == 1时
 // 判断采购订单是否存在,并且堆顶元素的售价要大于等于当前销售订单的单价
 while (!buy.isEmpty() && buy.top().price >= price && amount > 0) {
   let top = buy.pop()
	 // 如果采购订单数更多,则采购订单数、总订单数减少当前销售订单数的数量
   if (amount < top.amount) {
     buy.push({ price: top.price, amount: top.amount - amount })
     total -= amount
     amount = 0
   } else { // 相反采购订单全部被抵消,计算剩余的销售订单和总订单数
     amount -= top.amount
     total -= top.amount
   }
 }
	// 如果销售订单还有剩余,入堆。积压总订单数增加。
  if (amount > 0) {
    sellOrder.push({ price, amount })
    total += amount
  }
}

同理,当遇到时采购订单时,需要查看销售订单的堆顶。

如果该采购订单 buy 的价格 高于或等于 当前销售订单 sell 的价格,则匹配并执行这两笔订单,并将采购订单 buy 从积压订单中删除。否则,销售订单 sell 将会添加到积压订单中。

if (orderType == 0) {
      while (!sell.isEmpty() && sell.top().price <= price && amount > 0) {
        let top = sellOrder.pop();
        if (amount < top.amount) {
          sell.push({ price: top.price, amount: top.amount - amount })
          total -= amount
          amount = 0
        } else {
          amount -= top.amount;
          total -= top.amount
        }
      }
      if (amount > 0) {
        buy.push({ price, amount })
        total += amount
      }
    }

最后由于数量可能过大,所以需要对结果进行取余

const mod = 1000000007
return total % mod

总结起来就是

  • 对buy和sell分别创建大小顶堆。

  • 判断orders[i]的订单类型

  • 判断订单,查看对应堆顶,如果符合条件,进行数量抵消,重新添加剩余数量

    var getNumberOfBacklogOrders = function (orders) { const mod = 1000000007 const buyOrder = new Heap((a, b) => a.price < b.price); const sellOrder = new Heap((a, b) => a.price > b.price); let total = 0; for (let i = 0; i < orders.length; i++) { let [price, amount, orderType] = orders[i] if (orderType == 0) { while (!sellOrder.isEmpty() && sellOrder.top().price <= price && amount > 0) { let sell = sellOrder.pop(); if (amount < sell.amount) { sellOrder.push({ price: sell.price, amount: sell.amount - amount }) total -= amount amount = 0 } else { amount -= sell.amount; total -= sell.amount } } if (amount > 0) { buyOrder.push({ price, amount }) total += amount } } else { while (!buyOrder.isEmpty() && buyOrder.top().price >= price && amount > 0) { let buy = buyOrder.pop() if (amount < buy.amount) { buyOrder.push({ price: buy.price, amount: buy.amount - amount }) total -= amount amount = 0 } else { amount -= buy.amount total -= buy.amount } } if (amount > 0) { sellOrder.push({ price, amount }) total += amount } } } console.log(total % mod); return total % mod };