文章简要概述
- 本文主要进行队列相关的算法题刷题题解记录,带你进一步了解队列相关算法以及如何解。
- 这篇文章主要介绍leetcode中
设计循环队列、设计循环双端队列、和设计前中后队列的解题思路。
队列相关概念
队列(queue)是一种线性表,它的特性是先进先出,插入在一端,删除在另一端。就像排队一样,刚来的人入队(push)要排在队尾(rear),每次出队(pop)的都是队首(front)的人。特点:
- 队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构。
- 在队尾添加元素,在队头删除元素。
与队列相关算法
设计循环队列
题目大意:
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 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
解题思路:
-
借助一维数组来模拟循环队列结构,利用数组下标来模拟环。
-
根据队首位置、队列长度、数组长度,可以计算出队尾位置
tailIndex = (headIndex + L − 1) % max
代码:
function MyCircularQueue (k) {
this.list = new Array(k);
this.front = 0;
this.cont = 0;
this.max = k;
return this;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularQueue.prototype.enQueue = function(value) {
if (this.isFull()) {
return false
}
this.list[(this.front + this.cont) % this.max] = value;
this.cont++;
return true;
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.deQueue = function() {
if (this.isEmpty()) {
return false;
}
this.list[this.front] = null;
this.front = (this.front + 1) % this.max;
this.cont--;
return true
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Front = function() {
if (this.isEmpty()) {
return -1;
}
return this.list[this.front];
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Rear = function() {
if (this.isEmpty()) {
return -1;
}
const rear = (this.front + this.cont -1) % this.max;
return this.list[rear];
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isEmpty = function() {
return this.cont === 0;
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isFull = function() {
return this.cont === this.max;
};
设计循环双端队列
题目大意:
设计实现双端队列。
你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
示例:
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2
circularDeque.isFull(); // 返回 true
circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4
解题思路:
- 这道题和上一题的整体思路很像
front:指向队列头部第 1 个有效数据的位置rear:指向队列尾部(即最后 11 个有效数据)的 下一个位置,即下一个从队尾入队元素的位置- 判别队列为空的条件是:
front == rear;; - 判别队列为满的条件是:
(rear + 1) % max == front; - 指针后移的时候,下标 + 1,要取模;
代码:
class MyCircularDeque {
constructor(k){
this.max = k+1;
this.list = new Array(this.max);
this.front = 0; // 头部指向第 1 个存放元素的位置 //插入时,先减,再赋值 //删除时,索引 +1(注意取模)
this.rear= 0; // 尾部指向下一个插入元素的位置 // 插入时,先赋值,再加 // 删除时,索引 -1(注意取模)
}
insertFront(value){
if(this.isFull()){ return false }
this.front = (this.front-1+this.max)%this.max;
this.list[this.front] = value;
return true;
}
insertLast(value){
if(this.isFull()){ return false }
this.list[this.rear] = value;
this.rear = (this.rear+1) % this.max;
return true;
}
deleteFront(){
if(this.isEmpty()){ return false }
this.front = (this.front+1) % this.max;
return true;
}
deleteLast(){
if(this.isEmpty()) { return false};
this.rear = (this.rear-1+this.max) % this.max;
return true;
}
getFront(){
if(this.isEmpty()){ return -1};
return this.list[this.front];
}
getRear(){
if(this.isEmpty()){ return -1};
// 当 rear 为 0 时防止数组越界, rear指向的是下一个插入的元素的位置,元素为空,需要-1;
return this.list[(this.rear-1+this.max) % this.max];
}
isEmpty(){
return this.front == this.rear;
}
isFull(){
return (this.rear+1) % this.max == this.front;
}
}
设计前中后队列
题目大意:
请你设计一个队列,支持在前,中,后三个位置的 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 -> [] (队列为空)
解题思路:
- 这道题采用数组的方式也可以实现,但本题我们采用链表的方式模拟。
- 构建一个链表的方法。
- 建立虚拟的头节点作为我们队列的队首的前一项。
- 队首插入元素就是在虚拟头节点的后一位插入。
- 队中插入元素,我们可以用快慢指针,快指针是慢指针两倍速度,但快指针到链表尾部是,慢指针刚好到要插入的位置前一项。
- 队尾插入,遍历链表到尾部,插入即可
- 弹出元素的思路和插入的思路相差不大。
代码:
function NodeList (val, next) {
this.val = val;
this.next = next;
}
var FrontMiddleBackQueue = function() {
this.head = new NodeList(-1);
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushFront = function(val) {
this.head.next = new NodeList(val, this.head.next);
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
let slot = this.head;
let fast = this.head.next;
while(fast?.next) {
slot = slot.next;
fast = fast.next.next;
}
const middle = slot.next;
slot.next = new NodeList(val, middle);
};
/**
* @param {number} val
* @return {void}
*/
FrontMiddleBackQueue.prototype.pushBack = function(val) {
let p = this.head;
while(p.next) {
p = p.next;
}
p.next = new NodeList(val, null);
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popFront = function() {
const front = this.head.next;
if (!front) return -1;
this.head.next = front.next;
return front.val;
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popMiddle = function() {
let slot = this.head;
let fast = this.head.next;
if(fast === null) return -1
while(fast?.next) {
fast = fast.next.next;
if (fast) {
slot = slot.next;
}
}
const middle = slot.next;
slot.next = middle.next;
return middle.val;
};
/**
* @return {number}
*/
FrontMiddleBackQueue.prototype.popBack = function() {
let back = this.head;
if (!back.next) return -1;
while(back?.next?.next) {
back = back.next;
}
const val = back.next.val;
back.next = null;
return val;
};
结束语
数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是队列部分。后期还会有其他类型的数据结构,题目来源于leetcode。往期文章:
如果文章对你有帮助,欢迎点赞,关注!
文中图片资源来源于leetcode。