正题
设计前中后队列
请你设计一个队列,支持在前,中,后三个位置的 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]。
解析:
这个和普通队列以及双端队列不同的是,难点在于中间点,在队列元素的增加,减少的同时,中间点也会随之改变。说实话,解决这个问题花了比较多的时间,并不是因为题目的难度很大,是因为需要注意的细节很多,并且调试起来比较麻烦。
这题可以使用数组去解决,只要找到中间 middle 的位置其余采用数组的 shift, unshift, push, pop 就能解决 前后增删的问题,由于题目没有限制队列长队,所以相对来说还是比较简单的。
另外我们还可以使用单向链表的方式去解决:
构造链表数据结构
链表至少包含两个属性:
val 链表节点的数值
next 下一节点的指针
本题应用不到更复杂的链表结构,所以很简单的就能够造出一个链表数据结构。
var NodeList = function (val, next) {
this.val = val
this.next = next
}
构建完了之后,我们实践一下,如何通过链表去实现 三端队列!
初始化队列
FrontMiddleBackQueue 方法如何去实现呢,其实就是赋予队列一些字段属性。实际上,他只需要拥有一个链表属性就可以了,我们可以通过操作链表去实现队列的方法。所以很简单地:
var FrontMiddleBackQueue = function () {
this.head = null
};
head 表示链表头节点。默认 null,表示队列为空。
pushFront
该方法为在队列的头部添加节点,如果我们从链表的方向去看,即在 head 节点前面再添加一个 next 指针指向 head 节点的节点 ,听上去比较拗口,但是很容易理解。我们用图解来表达:
上图一目了然,当链表节点为空,那么直接 head 指向新节点,如果不为空,那么创建一个节点指向 head。
这两种情况我们可以用一行代码解决:
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushFront = function (val) {
this.head = new NodeList(val, this.head)
};
实际上原理都是一样的,只不过空链表的 head 为 null
pushMiddle
本题重点来了。在链表的中间节点插入节点。
我们可以知道,中间这个词是有定义的,也就是说它规定了一个特殊规则,当节点数量为奇数个:
- 将
6添加到[1, 2, 3, 4, 5]的中间位置,结果数组为[1, 2, 6, 3, 4, 5]。
由于链表不去遍历我们是不知道它的长度的,如果去遍历就为了寻找长度,就会对性能上造成很大损失。所以我们要找到一种解决办法,无论是奇数还是偶数都能够找到中见节点!
还记得快慢指针吗?快慢指针不仅适用于找到倒数第几个节点,也适用于找到中间节点。所以我们可以考虑使用双指针的方法。
偶数个:
奇数个:
原理一样,我们只需要考虑 Fast 指针 下一个节点 和 下下个节点为空的时候, Slow 就为带插入节点的位置了。
另外要考虑的细节就是,当链表为空以及链表仅有一个节点的时候。
当链表为空,我们可以认为就是 pushFront,当链表只有一个节点我们也可以认为是 pushFront。
最终图解:
实现:
FrontMiddleBackQueue.prototype.pushMiddle = function (val) {
if (!this.head || !this.head.next) {
this.pushFront(val)
return
}
let slow = this.head
let fast = this.head.next
while (fast && fast.next && fast.next.next) {
slow = slow.next
fast = fast.next.next
}
slow.next = new NodeList(val, slow.next)
console.log('push middle', this.head)
};
pushBack
比较简单的实现,即在链表尾部 next 指针指向新节点即可,考虑空队列的话直接就是 this.head 等于新节点。
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushBack = function (val) {
if (!this.head) {
this.pushFront(val)
return
}
let p = this.head
while(p.next) {
p = p.next
}
p.next = new NodeList(val, null)
};
popFont
弹出并返回队列的第一个节点。返回很简单,直接 return this.head.val,但我们还需要将第一个节点删除掉,删除链表的 head 节点,直接将 head 指向 head.next 即可,考虑细节,当链表为空,则直接返回 -1
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popFront = function () {
if (!this.head) {
return -1
}
const res = this.head.val
this.head = this.head.next
return res
};
popMiddle
本题另一个难点,找 popMiddle 节点位置,同样的,我们需要找到奇数和偶数个节点时,都能够准确找到 middle 的位置。
如图,和 pushMiddle 不同的是,fast 从第三个节点开始
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popMiddle = function () {
if (!this.head) {
return -1
}
if (!this.head.next || !this.head.next.next) {
// 仅有一个元素
return this.popFront()
}
let slow = this.head
let fast = this.head.next.next
while (fast && fast.next && fast.next.next) {
slow = slow.next
fast = fast.next.next
}
const res = slow.next
slow.next = slow.next.next
console.log('pop middle', this.head)
return res.val
};
另外在考虑空队列和仅有一个元素的队列即可
popBack
弹出最后一个节点。按照链表的性质,遍历得到,return 即可
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popBack = function () {
if (!this.head) {
return -1
}
if (!this.head.next) {
return this.popFront()
}
let backPre = this.head
let back = this.head.next
while (back.next) {
backPre = backPre.next
back = back.next
}
backPre.next = null
console.log('pop back', this.head)
return back.val
};
以上完成了所有三端队列的所有方法。
完整代码:
var NodeList = function (val, next) {
this.val = val
this.next = next
}
var FrontMiddleBackQueue = function () {
this.head = null
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushFront = function (val) {
this.head = new NodeList(val, this.head)
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushMiddle = function (val) {
if (!this.head || !this.head.next) {
this.pushFront(val)
return
}
let slow = this.head
let fast = this.head.next
while (fast && fast.next && fast.next.next) {
slow = slow.next
fast = fast.next.next
}
slow.next = new NodeList(val, slow.next)
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushBack = function (val) {
if (!this.head) {
this.pushFront(val)
return
}
let p = this.head
while(p.next) {
p = p.next
}
p.next = new NodeList(val, null)
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popFront = function () {
if (!this.head) {
return -1
}
const res = this.head.val
this.head = this.head.next
return res
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popMiddle = function () {
if (!this.head) {
return -1
}
if (!this.head.next || !this.head.next.next) {
// 仅有一个元素
return this.popFront()
}
let slow = this.head
let fast = this.head.next.next
while (fast && fast.next && fast.next.next) {
slow = slow.next
fast = fast.next.next
}
const res = slow.next
slow.next = slow.next.next
return res.val
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popBack = function () {
if (!this.head) {
return -1
}
if (!this.head.next) {
return this.popFront()
}
let backPre = this.head
let back = this.head.next
while (back.next) {
backPre = backPre.next
back = back.next
}
backPre.next = null
return back.val
};
/**
* 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()
*/
// const queue = new FrontMiddleBackQueue()
// queue.pushFront(1)
// queue.pushBack(2)
// queue.pushMiddle(3)
// queue.pushMiddle(4)
// console.log(queue.popFront())
// console.log(queue.popMiddle())
// console.log(queue.popMiddle())
// console.log(queue.popBack())
// console.log(queue.popFront())