用JavaScript刷leetcode第1670题-设计前中后队列

471 阅读4分钟

一、前言

其实用JavaScript的数组实现前中后队列很简单,因为JavaScript数组本身就支持前中后的操作。可以用一个数组实现,用两个数组实现意义不是很大。
这里我将用循环双端队列来实现前中后队列。我之前有用数组实现过循环双端队列,这里我就用双向链表来实现这个循环双端队列。

二、题目描述

请你设计一个队列,支持在前,中,后三个位置的 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] 。 实例:

输入:
["FrontMiddleBackQueue", "pushFront", "pushBack", "pushMiddle", "pushMiddle", "popFront", "popMiddle", "popMiddle", "popBack", "popFront"]
[[], [1], [2], [3], [4], [], [], [], [], []]
输出:
[null, null, null, null, null, 1, 3, 4, 2, -1]

解释:
FrontMiddleBackQueue q = new FrontMiddleBackQueue();
q.pushFront(1);   // [1]
q.pushBack(2);    // [1, 2]
q.pushMiddle(3);  // [1, 3, 2]
q.pushMiddle(4);  // [1, 4, 3, 2]
q.popFront();     // 返回 1 -> [4, 3, 2]
q.popMiddle();    // 返回 3 -> [4, 2]
q.popMiddle();    // 返回 4 -> [2]
q.popBack();      // 返回 2 -> []
q.popFront();     // 返回 -1 -> [] (队列为空)

如果想要看更详细的题解,请看leetcode地址

三、解题

3.1 实现链表节点类

有注释,该类就是构造函数加4个方法

// 链表节点类
class Node {
  constructor(val = 0, next = null, pre = null) {
    this.val = val
    this.next = next
    this.pre = pre
  }
  // 当前节点前插入节点
  insertBefore(node) {
    // 插入操作先处理待插入节点,最后处理当前节点
    node.pre = this.pre
    node.next = this
    if(this.pre) this.pre.next = node
    this.pre = node 
  }
  // 当前节点后插入节点
  insertAfter(node) {
    // 插入操作先处理待插入节点,最后处理当前节点
    node.pre = this
    node.next = this.next
    if(this.next) this.next.pre = node
    this.next = node
  }
  // 删除当前节点前一个节点
  deleteBefore() {
    if(this.pre) {
      this.pre = this.pre.pre
      if(this.pre) this.pre.next = this
    }
  }

  // 删除当前节点后一个节点
  deleteAfter() {
    if(this.next) {
      this.next = this.next.next
      if(this.next) this.next.pre = this
    }
  }
}

3.2 用链表实现简版循环双端链表

注意虚头虚尾就好了

// 循环双端队列
class CircularDoubleEndQueue {
  constructor() {
    this.count = 0
    // 虚头
    this.dummyHead = new Node()
    // 虚尾
    this.dummyTail = new Node()
    // 初始时,虚头指向虚尾
    this.dummyHead.next = this.dummyTail
    this.dummyTail.pre = this.dummyHead
  }

  // 队首增
  pushFront(val) {
    this.dummyHead.insertAfter(new Node(val))
    this.count++
  }
  
  // 队尾增
  pushBack(val) {
    this.dummyTail.insertBefore(new Node(val))
    this.count++
  }

  // 队首删
  popFront() {
    // 队列为空
    if(this.isEmpty()) return -1
    // 保存删除节点值
    const ret = this.dummyHead.next.val

    // 删除
    this.dummyHead.deleteAfter()
    this.count--

    return ret
  }


  // 队尾删
  popBack(val) {
    // 队列为空
    if(this.isEmpty()) return -1
    // 保存删除节点值
    const ret = this.dummyTail.pre.val

    // 删除
    this.dummyTail.deleteBefore()
    this.count--

    return ret
  }

  // 判空
  isEmpty() {
    // this.count === 0
    // this.dummyTail.pre === this.dummyHead
    return this.dummyHead.next === this.dummyTail
  }
}

3.3 用循环双端队列实现前中后队列

这里我不想多说,看代码的时候心中要想着进行这个操作会不会不满足:

  • 左队列数小于等于右队列数
  • 左队列数 最小 为 右队列数数目 - 1
  • 右队列数 最大 为 左队列 + 1
var FrontMiddleBackQueue = function() {
  // 初始化两个循环双端队列
  this.leftQue = new CircularDoubleEndQueue()
  this.rightQue = new CircularDoubleEndQueue()
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushFront = function(val) {
  // 前增
  this.leftQue.pushFront(val)
  // 增后可能leftQue.count > rightQue.count
  if(this.leftQue.count > this.rightQue.count) this.rightQue.pushFront(this.leftQue.popBack())
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
  // 中增分情况,当leftQue.count = rightQue.count 右队列首增;leftQue.count < rightQue.count 左队列尾增, 这两种情况都满足 leftQue.count < rightQue.count - 1, 故不需要处理
  if(this.leftQue.count === this.rightQue.count) {
    this.rightQue.pushFront(val)
  } else {
    this.leftQue.pushBack(val)
  }
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushBack = function(val) {
  // 尾增
  this.rightQue.pushBack(val)
  // 增后this.leftQue.count < this.rightQue.count - 1
  if(this.leftQue.count < this.rightQue.count - 1) this.leftQue.pushBack(this.rightQue.popFront())
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popFront = function() {
  // 左队列右队列都为空,返回-1
  if(this.rightQue.isEmpty()) return -1
  // 右队列不为空,左队列为空,右队列首出队
  if(this.leftQue.isEmpty()) return this.rightQue.popFront()

  // 左右都不为空,左队列首出队,出对后可能this.leftQue.count < this.rightQue.count - 1
  let ret = this.leftQue.popFront()
  if(this.leftQue.count < this.rightQue.count - 1) this.leftQue.pushBack(this.rightQue.popFront())
  return ret
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popMiddle = function() {
  // 左队列右队列都为空,返回-1
  if(this.rightQue.isEmpty()) return -1
  // 左队列元素数量 等于 右队列元素数量 左队列尾出队
  if(this.leftQue.count === this.rightQue.count) return this.leftQue.popBack()
  // 左队列小于右队列数,右队列首出队
  return this.rightQue.popFront()
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popBack = function() {
  // 左队列右队列都为空,返回-1
  if(this.rightQue.isEmpty()) return -1
  // 右队列不为空,右队列尾出队
  let ret = this.rightQue.popBack()
  // 右队列尾出对后,可能this.leftQue.count > this.rightQue.count
  if(this.leftQue.count > this.rightQue.count) this.rightQue.pushFront(this.leftQue.popBack())
  return ret
};

/**
 * Your FrontMiddleBackQueue object will be instantiated and called as such:
 * var obj = new FrontMiddleBackQueue()
 * obj.pushFront(val)
 * obj.pushMiddle(val)
 * obj.pushBack(val)
 * var param_4 = obj.popFront()
 * var param_5 = obj.popMiddle()
 * var param_6 = obj.popBack()
 */

3.4 完整代码

其实我本来准备就把这一坨代码放上去的,又怕到时候自己复习时自己都不要愿意看,这个代码我花了2个小时写,自己还是太菜了。。。

// 链表节点类
class Node {
  constructor(val = 0, next = null, pre = null) {
    this.val = val
    this.next = next
    this.pre = pre
  }
  // 当前节点前插入节点
  insertBefore(node) {
    // 插入操作先处理待插入节点,最后处理当前节点
    node.pre = this.pre
    node.next = this
    if(this.pre) this.pre.next = node
    this.pre = node 
  }
  // 当前节点后插入节点
  insertAfter(node) {
    // 插入操作先处理待插入节点,最后处理当前节点
    node.pre = this
    node.next = this.next
    if(this.next) this.next.pre = node
    this.next = node
  }
  // 删除当前节点前一个节点
  deleteBefore() {
    if(this.pre) {
      this.pre = this.pre.pre
      if(this.pre) this.pre.next = this
    }
  }

  // 删除当前节点后一个节点
  deleteAfter() {
    if(this.next) {
      this.next = this.next.next
      if(this.next) this.next.pre = this
    }
  }
}

// 循环双端队列
class CircularDoubleEndQueue {
  constructor() {
    this.count = 0
    // 虚头
    this.dummyHead = new Node()
    // 虚尾
    this.dummyTail = new Node()
    // 初始时,虚头指向虚尾
    this.dummyHead.next = this.dummyTail
    this.dummyTail.pre = this.dummyHead
  }

  // 队首增
  pushFront(val) {
    this.dummyHead.insertAfter(new Node(val))
    this.count++
  }
  
  // 队尾增
  pushBack(val) {
    this.dummyTail.insertBefore(new Node(val))
    this.count++
  }

  // 队首删
  popFront() {
    // 队列为空
    if(this.isEmpty()) return -1
    // 保存删除节点值
    const ret = this.dummyHead.next.val

    // 删除
    this.dummyHead.deleteAfter()
    this.count--

    return ret
  }


  // 队尾删
  popBack(val) {
    // 队列为空
    if(this.isEmpty()) return -1
    // 保存删除节点值
    const ret = this.dummyTail.pre.val

    // 删除
    this.dummyTail.deleteBefore()
    this.count--

    return ret
  }

  // 判空
  isEmpty() {
    // this.count === 0
    // this.dummyTail.pre === this.dummyHead
    return this.dummyHead.next === this.dummyTail
  }
}


// ********************* 有了前面的铺垫,现在开始实现前中后队列 ***************************



var FrontMiddleBackQueue = function() {
  // 初始化两个循环双端队列
  this.leftQue = new CircularDoubleEndQueue()
  this.rightQue = new CircularDoubleEndQueue()
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushFront = function(val) {
  // 前增
  this.leftQue.pushFront(val)
  // 增后可能leftQue.count > rightQue.count
  if(this.leftQue.count > this.rightQue.count) this.rightQue.pushFront(this.leftQue.popBack())
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
  // 中增分情况,当leftQue.count = rightQue.count 右队列首增;leftQue.count < rightQue.count 左队列尾增, 这两种情况都满足 leftQue.count < rightQue.count - 1, 故不需要处理
  if(this.leftQue.count === this.rightQue.count) {
    this.rightQue.pushFront(val)
  } else {
    this.leftQue.pushBack(val)
  }
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushBack = function(val) {
  // 尾增
  this.rightQue.pushBack(val)
  // 增后this.leftQue.count < this.rightQue.count - 1
  if(this.leftQue.count < this.rightQue.count - 1) this.leftQue.pushBack(this.rightQue.popFront())
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popFront = function() {
  // 左队列右队列都为空,返回-1
  if(this.rightQue.isEmpty()) return -1
  // 右队列不为空,左队列为空,右队列首出队
  if(this.leftQue.isEmpty()) return this.rightQue.popFront()

  // 左右都不为空,左队列首出队,出对后可能this.leftQue.count < this.rightQue.count - 1
  let ret = this.leftQue.popFront()
  if(this.leftQue.count < this.rightQue.count - 1) this.leftQue.pushBack(this.rightQue.popFront())
  return ret
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popMiddle = function() {
  // 左队列右队列都为空,返回-1
  if(this.rightQue.isEmpty()) return -1
  // 左队列元素数量 等于 右队列元素数量 左队列尾出队
  if(this.leftQue.count === this.rightQue.count) return this.leftQue.popBack()
  // 左队列小于右队列数,右队列首出队
  return this.rightQue.popFront()
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popBack = function() {
  // 左队列右队列都为空,返回-1
  if(this.rightQue.isEmpty()) return -1
  // 右队列不为空,右队列尾出队
  let ret = this.rightQue.popBack()
  // 右队列尾出对后,可能this.leftQue.count > this.rightQue.count
  if(this.leftQue.count > this.rightQue.count) this.rightQue.pushFront(this.leftQue.popBack())
  return ret
};

/**
 * Your FrontMiddleBackQueue object will be instantiated and called as such:
 * var obj = new FrontMiddleBackQueue()
 * obj.pushFront(val)
 * obj.pushMiddle(val)
 * obj.pushBack(val)
 * var param_4 = obj.popFront()
 * var param_5 = obj.popMiddle()
 * var param_6 = obj.popBack()
 */