一、前言
其实用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()
*/