前言
链表相关的题暂时先告一段落(后面会复习),今天我们进入队列和栈的相关算法题,先从队列开始
队列的定义:
对列: 是一种特殊的线性表,只能从队首删除元素,队尾插入元素,遵循FIFO先入先出的原则;
在JavaScript种我们可以用数组来模拟队列
循环队列: 是队列的升级版,队尾连接在队首之后,形成一个闭环,那么只有队尾后有剩余空间我们就可以使用,而普通队列在队尾指针走到队列的末端就插入不了元素了
题目描述
622. 设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
示例:
MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1); // 返回 true
circularQueue.enQueue(2); // 返回 true
circularQueue.enQueue(3); // 返回 true
circularQueue.enQueue(4); // 返回 false,队列已满
circularQueue.Rear(); // 返回 3
circularQueue.isFull(); // 返回 true
circularQueue.deQueue(); // 返回 true
circularQueue.enQueue(4); // 返回 true
circularQueue.Rear(); // 返回 4
解题思路
- 首先得建个数组来模拟队列,
对首指针front指向队列第一个元素,队尾rear指针指向最后一个元素的下一位 - 那么数组长度弄多少呢? 用队列长度k+1,但是数组最大填充长度是max = k,原因见下条
- 队列判满: (rear-front+max+1)%(max+1) 的值为max=k
例如: max=k=4 队列长队则为5,元素最大下标4,最小为0:
rear=4,front=0队列填满(rear-front+5)%5=4;
rear=0,front=1队列填满(0-1+5)%5=4;
取余加max+1的原因(懂了就这题就拿捏了) 为了避免上面rear-front出现负数的情况,加上循环队里的长度再取余即得所占队列长度,rear-front正数结果就是占用队列长度再加上一个必定大于他的max+1取余则还是原来的正数 - 队列判空: (rear-front+max+1)%(max+1) 值为 0
- 删除元素 队首指针前移1位,添加元素 直接队尾指针位置复制然后将队尾指针后移1位
开始解题
/**
* @param {number} k
*/
var MyCircularQueue = function(k) {
this.queue = new Array(k+1);
this.front = 0;
this.rear = 0;
this.max = k;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularQueue.prototype.enQueue = function(value) {
if(this.isFull()) return false;
let cur = (this.rear+1) % (this.max+1);
this.queue[this.rear] = value;
this.rear = cur;
return true;
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.deQueue = function() {
if(this.isEmpty()) return false;
this.front = (this.front+1) % (this.max + 1); // 加this.max+1就是为了循环起来,走到底+1=this.max+1刚好取余归0重新开始
return true;
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Front = function() {
if(this.isEmpty()) return -1;
return this.queue[this.front];
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Rear = function() { // this.rear指针指向尾元素后一位
if(this.isEmpty()) return -1;
return this.queue[(this.rear+this.max) % (this.max+1)] // 原代码 (this.rear-1+this.max+1)%(this.max+1)
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isEmpty = function() {
return (this.rear-this.front+this.max+1) % (this.max + 1) === 0;
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isFull = function() {
return (this.rear-this.front+this.max+1) % (this.max + 1) === this.max;
};
641. 设计循环双端队列
解题思路:
- 循环队列是后入前出的FIFO,其实双端循环队列就是前后均可出入队列,多了前入后出,在循环队列的解题逻辑加上即可
添加前入后出代码:
MyCircularQueue.insertFront = function(value){ // 对首入队,头指针前移1位
if(this.isFull()) return false;
let temp = (this.front-1 + this.max +1) % (this.max+1); // -1+1可省略这里为了好理解
this.queue[temp] = value;
this.front = temp;
return true;
}
MyCircularQueue.deleteLast = function(){ // 对尾出队,尾指针前移1位
if(this.isEmpty()) return false;
this.rear = (this.rear-1+this.max+1) % (this.max+1);
return true;
}
1670. 设计前中后队列
请你设计一个队列,支持在前,中,后三个位置的 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] 。
解题思路:
-
上方红色区域为重点思路体现,全局围绕此操作,决定中间的出入队操作
-
因为一个数组我们不好判断正中间的位置,所以需要用到
左右两个数组来模拟前中后队列 -
如何判断正中间的操作?队列为奇数长度时:
[1, 2, 3, 4, 5]在中间值的左边即小于数组长度一半的位置增加了6 =》[1, 2, 6, 3, 4, 5]
队列为偶数长度时:
[1,2,3,4,5,6]弹出的是靠左边中间值的3,让左边一半小于原数组长度 =》[1,2,4,5,6]由此可得,在队列操作过程中,左边队列属组的长度一定是小于或等于右边数组的长度的,那么我们在编写代码的时候围绕这一点进行即可。 -
由题目描述本题我们只需对队列进行判空操作
-
队首和队尾出入队我们先执行出入队操作再平衡左右数组长度,左边数组头节点为队首,右边数组尾节点为队尾
-
队中间出入队需要先判断左右数组的长度,分为两种情况考虑:左边小于右边和左边等于右边
开始解题
var FrontMiddleBackQueue = function() {
this.leftQ = [];
this.rightQ = [];
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushFront = function(val) {
this.leftQ.unshift(val); // 队首先入队
if(this.leftQ.length > this.rightQ.length) { // 左边大于时,左边队尾出队一个添加到右边的队首
this.rightQ.unshift(this.leftQ.pop())
}
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
if(this.rightQ.length == this.leftQ.length) { // 队中间入队当左右数组长度相等时,添加到右边队首
this.rightQ.unshift(val);
}else { // 否则添加到左边队尾
this.leftQ.push(val);
}
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushBack = function(val) {
this.rightQ.push(val);
if(this.leftQ.length+2 == this.rightQ.length) { // 当右边数组长度等于比左边大2时,右边队首出队一个添加到左边队尾
this.leftQ.push(this.rightQ.shift());
}
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popFront = function() {
if(this.rightQ.length === 0) return -1; // 因为右边数组长度始终大于等于左边,所以判空只需右边为0
if(!this.leftQ.length) return this.rightQ.pop(); // 当左边数组长度为0时,直接让右边数组出队
let popVal = this.leftQ.shift();
if(this.leftQ.length+1 < this.rightQ.length){
this.leftQ.push(this.rightQ.shift());
}
return popVal;
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popMiddle = function() {
if(this.rightQ.length === 0) return -1;
let popVal;
if(this.leftQ.length < this.rightQ.length) {
popVal = this.rightQ.shift();
}else {
popVal = this.leftQ.pop();
}
return popVal;
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popBack = function() {
if(this.rightQ.length === 0) return -1;
let popVal = this.rightQ.pop();
if(this.rightQ.length < this.leftQ.length) {
this.rightQ.unshift(this.leftQ.pop());
}
return popVal;
};