[路飞]LeetCode-1670 设计前中后队列(今天来个膀胱局)

373 阅读4分钟

题目描述

请你设计一个队列,支持在前,中,后三个位置的 push 和 pop 操作。

请你完成 FrontMiddleBack 类:

FrontMiddleBack() 初始化队列。
void pushFront(int val) 将 val 添加到队列的 最前面 。
void pushMiddle(int val) 将 val 添加到队列的 正中间 。
void pushBack(int val) 将 val 添加到队里的 最后面 。
int popFront() 将 最前面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popMiddle() 将 正中间 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popBack() 将 最后面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。

请注意当有 两个 中间位置的时候,选择靠前面的位置进行操作。比方说:

将 6 添加到 [1, 2, 3, 4, 5] 的中间位置,结果数组为 [1, 2, 6, 3, 4, 5] 。

从 [1, 2, 3, 4, 5, 6] 的中间位置弹出元素,返回 3 ,数组变为 [1, 2, 4, 5, 6] 。

思路分析

这道题可以使用我们之前那道,设计双端队列来解决,两个双端队列组合成一个前中后队列。

第一个双端队列和第二个双端队列可以设计为,如果两数相等,则pushMiddle到第一个双端队列的尾部,如果第一个队列大于第二个,则将数据放在第二个数据的头部。

因此我们会在这道题里面先封装一个双端队列,封装双端队列也可以用链表来实现。

因此我们可以先实现一个链表结构,再用链表来实现一个双端队列,再实现这道题目。

好了,俄罗斯套套娃。

接下来我们先来实现链表的封装。

不会的可以转移阵地先去学习链表了,饭要一口一口吃,路要一步一步走。

链表封装

创建一个TreeNode类,包含,pre、next、val三个属性。

包含:

insert_pre、insert_next、delete_pre、delete_next

insert_pre

insert_pre.gif

insert_next

insert_next.gif

delete_pre

image.png

delete_next

image.png


class TreeNode {
    constructor(val, next, pre) {
        this.val = val;
        this.next = next;
        this.pre = pre;
    }
    
    insert_pre = (node) => {
        node.next = this;
        node.pre = this.pre;
        if (this.pre) {
            this.pre.next = node
        }
        this.pre = node;
        return;
    }
    
    insert_next = (node) => {
        next.pre = this;
        next.next = this;
        if (this.pre) this.next.pre = node;
        this.next = node;
    }
    
    delete_pre = () => {
        if (!this.pre) return;
        const node = this.pre;
        this.pre = node.pre;
        if (node.pre) node.pre.next = this;
        return;
    }

    delete_next = () => {
        if (!this.next) return;
        const node = this.next;
        this.next = node.next;
        if (node.next) node.next.pre = this;
        return;
    }
}

使用链表实现的双端队列封装

我们可以提纲挈领的来复习一下之前实现的双端队列了,首先,一个双端队列需要有两个节点,头节点head和尾节点tail

初始化的时候,头节点的上一个为空,尾节点的下一个为空,而头节点的下一个是尾节点,尾节点的上一个是头节点。

我不套娃了!看看单独实现的双端队列封装知识点

除此之外还有辅助参数count,用于来存储当前双端队列的大小

双端队列的类名叫做DeQueue,属性上面已经讲完了,下面来看双端队列需要的方法

pushBack

为虚拟尾部节点插入新的对象

pushFount

为虚拟头部节点插入新的对象

popBack

删除尾部节点

popFront

删除头部节点

isEmpty

是否是空队列

size

队列的大小

front

查看头部节点

back

查看尾部节点

代码安排

下面我们来用代码来实现一下


class DeQueue {
    constructor() {
        this.head = new TreeNode();
        this.tail = new TreeNode();
        this.head.next = this.tail;
        this.tail.pre = this.head;
        this.head.pre = null;
        this.tail.next = null;
        this.count = 0;
    }
    
    pushBack = (val) => {
        this.tail.insert_pre(new TreeNode(val));
        this.count += 1;
    }
    
    pushFront = (val) => {
         this.head.insert_next(new TreeNode(val));
         this.count += 1;
    }
    
    popBack = () => {
        if (this.isEmpty()) return;
        const ret = this.tail.pre.val;
        this.tail.delete_pre();
        this.count -= 1;
        return ret;
    };

    popFront = () => {
        if (this.isEmpty()) return;
        const ret = this.head.next.val;
        this.head.delete_next();
        this.count -= 1;
        return ret;
    };

    isEmpty = () => {
        return this.head.next === this.tail;
    };

    size = () => {
        return this.count;
    };

    front = () => {
        return this.head.next.val;
    };

    back = () => {
        return this.tail.pre.val;
    };
}

使用封装好了的双端队列来实现我们的前中后队列

需要实现的题目里面已经讲了,思路在本篇文章的开头大概描述了一下,需要用两个双端队列来实现,除了正常来实现我们的诉求方法之外,针对两个双端队列数据的平衡我们可以再增加一个update方法。

方法用来更新两个队列中的元素数量

下面来实现我们的FrontMiddleBackQueue


class FrontMiddleBackQueue {
      constructor() {
          this.q1 = new DeQueue();
          this.q2 = new DeQueue();
      }

      // 两个双端队列 Q1 Q2
      // @lc code=start
      /**
       * @param {number} val
       * @return {void}
       */
      pushFront = (val) => {
          this.q1.pushFront(val);
          this.update();
          return;
      };

      /**
       * @param {number} val
       * @return {void}
       */
      pushBack = (val) => {
          this.q2.pushBack(val);
          this.update();
          return;
      };

      /**
       * @param {number} val
       * @return {void}
       *
       */
      pushMiddle = (val) => {
          if (this.q1.size() > this.q2.size()) {
            this.q2.pushFront(this.q1.popBack());
          }

          this.q1.pushBack(val);
          this.update();
          return;
      };

      /**
       * @return {number}
       */
      popFront = () => {
          if (this.isEmpty()) return -1;
          const ret = this.q1.popFront();
          this.update();
          return ret;
      };

      /**
       * @return {number}
       */
      popBack = () => {
          if (this.isEmpty()) return -1;
          let ret;
          if (this.q2.isEmpty()) {
            ret = this.q1.popBack();
          } else {
            ret = this.q2.popBack();
          }
          this.update();
          return ret;
      };

      /**
       * @return {number}
       */
      popMiddle = () => {
          if (this.isEmpty()) return -1;
          const ret = this.q1.popBack();
          this.update();
          return ret;
      };

      isEmpty = () => {
          return this.q1.size() + this.q2.size() === 0;
      };

      update = () => {
          // 更新元素数量,Q1奇数,偶数均分
          if (this.q1.size() < this.q2.size()) {
            this.q1.pushBack(this.q2.popFront());
          }

          if (this.q1.size() === this.q2.size() + 2) {
            this.q2.pushFront(this.q1.popBack());
          }
      };
}

其他相关

全套代码实现