题目描述
请你设计一个队列,支持在前,中,后三个位置的 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_next
delete_pre
delete_next
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());
}
};
}