[路飞]_设计前中后队列

90 阅读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] 。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/de… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

  1. 用两个循环双端队列实现前中后队列,循环双端队列用链表实现
  2. 先实现循环双端队列,
    • 要有两个虚拟节点,分别是head和tail,head.next => tail tail.pre => head,判空条件为head.next === tial.pre
    • 节点类 需要实现插入前一个简单,插入后一个节点,删除上一个节点,删除下一个节点的方法
    • 双端队列类需要实现,头部插入,尾部插入,元素大小,判空,获取队首元素,队尾元素,队首元素,队尾元素删除方法
  3. 前中后队列实现
    • 用两个循环双端队列去模拟,左边的元素要比右边的元素多一个或者相等,如果不在这两种情况中的话,就是通过update方法去调整两个队列,左边比右边小的话,就取右边的首元素放到左边的尾部,左边比右边大于1的话,就是左边尾部元素删除,右边头部插入
    • 中间插入的方法,判断左边是否比右边打,如果是就把左边的尾部插入到右边的头部
    • 头部插入就是左边队列头部插入,然后调用update方法。尾部插入也是同理,右边尾部插入,然后调用update方法
    • 其他的方法就不说了,比较容易理解了

代码

// Node类
var Node = function(val, pre = null, next = null) {
  this.val = val
  this.pre = pre
  this.next = next
  this.insertPre = function(val) {
    const pre = this.pre
    this.pre = val
    val.next = this
    if (pre) pre.next = val
    // pre.next = pre
    val.pre = pre
  }
  this.inserNext = function(val) {
    const next = this.next
    this.next = val
    val.pre = this
    if (next) next.pre = val
    val.next = next
  }
  this.delPre = function() {
    // 删除当前节点的前一个节点
    if (!this.pre) return -1
    // p是当前要删除的节点
    const p = this.pre
    this.pre = p.pre
    // 有可能是空所以需要判断一下

    if (this.pre) this.pre.next = this
    return p
  }
  this.delNext = function() {
    if (!this.next) return -1
    const p = this.next
    this.next = p.next
    if (this.next) this.next.pre = this
    return p
  }
}
// 循环双端队列类
var MyCircularDeque = function() {
    this.head = new Node('head')
    this.tail = new Node('tail')
    this.head.next = this.tail
    this.tail.pre = this.head
    this.cnt = 0 
};

/** 
 * @param {number} value
 * @return {boolean}
 */
MyCircularDeque.prototype.insertFront = function(value) {
    this.head.inserNext(new Node(value))
    this.cnt++
    return true
};

/** 
 * @param {number} value
 * @return {boolean}
 */
MyCircularDeque.prototype.insertLast = function(value) {
    this.tail.insertPre(new Node(value))
    this.cnt++
    return true
};

/**
 * @return {boolean}
 */
MyCircularDeque.prototype.deleteFront = function() {
    if (this.isEmpty()) return false
    let val = this.getFront()
    this.head.delNext()
    this.cnt--
    return val
};

/**
 * @return {boolean}
 */
MyCircularDeque.prototype.deleteLast = function() {
    if (this.isEmpty()) return false
    let val = this.getRear()
    this.tail.delPre()
    this.cnt--
    return val
};

/**
 * @return {number}
 */
MyCircularDeque.prototype.getFront = function() {
    if (this.isEmpty()) return -1
    return this.head.next.val
};

/**
 * @return {number}
 */
MyCircularDeque.prototype.getRear = function() {
    if (this.isEmpty()) return -1
    return this.tail.pre.val
};

/**
 * @return {boolean}
 */
MyCircularDeque.prototype.isEmpty = function() {
    return this.head.next === this.tail
};


MyCircularDeque.prototype.size = function() {
  return this.cnt
}

// 前中后队列
class FrontMiddleBackQueue {
  constructor() {
    this.q = new MyCircularDeque()
    this.q2 = new MyCircularDeque()
  }

  pushFront(value) {
    this.q.insertFront(value)
    this.update()
  }

  pushMiddle(value) {
    // 因为中间始终在队列1中,所以先判断q的size是否比q2大如果是,就先把q的尾部元素放到q2头部去
    if (this.q.size() > this.q2.size()) {
      this.q2.insertFront(this.q.deleteLast())
    }
    this.q.insertLast(value)
    // this.q2.insertLast(value)
    // this.pushBack(value)
    // this.pushFront(value)
  }

  pushBack(value) {
    this.q2.insertLast(value)
    this.update()
  }

  popFront() {
    if (this.isEmpty()) return -1
    let res = this.q.deleteFront()
    this.update()
    return res
  }

  popMiddle() {
    if (this.isEmpty()) return -1
    let res = this.q.deleteLast()
    this.update()
    return res
  }
  popBack() {
    if (this.isEmpty()) return -1
    let res = this.q2.isEmpty() ? this.q.deleteLast() : this.q2.deleteLast()
    
    this.update()
    return res
  }
  update() {
    if (this.q.size() < this.q2.size()) {
      this.q.insertLast(this.q2.deleteFront())
    }
    if (this.q.size() === (this.q2.size() + 2)) {
      let val = this.q.deleteLast()
      this.q2.insertFront(val)
    }
  }
  isEmpty() {
    return this.q.size() === 0
  }
}

解题收获

这个解法应该是比较麻烦的了,如果用数组去实现的话,应该就不需要实现,node类和循环双队列类。循环双队列也是用链表去实现的。昨天的循环双队列是用数组实现,感觉对链表和队列的这种数据结构有了更深一点的认识,一个算法可以用不同的思想去实现。同时也感觉对自己的编码能力也是一种考验。