「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」
设计循环队列 Design Circular Queue
LeetCode传送门622. 设计循环队列
题目
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
- MyCircularQueue(k): 构造器,设置队列长度为 k 。
- Front: 从队首获取元素。如果队列为空,返回 -1 。
- Rear: 获取队尾元素。如果队列为空,返回 -1 。
- enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
- deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
- isEmpty(): 检查循环队列是否为空。
- isFull(): 检查循环队列是否已满。
Design your implementation of the circular queue. The circular queue is a linear data structure in which the operations are performed based on FIFO (First In First Out) principle and the last position is connected back to the first position to make a circle. It is also called "Ring Buffer".
One of the benefits of the circular queue is that we can make use of the spaces in front of the queue. In a normal queue, once the queue becomes full, we cannot insert the next element even if there is a space in front of the queue. But using the circular queue, we can use the space to store new values.
Implementation the MyCircularQueue class:
-
MyCircularQueue(k) Initializes the object with the size of the queue to be k.
-
int Front() Gets the front item from the queue. If the queue is empty, return -1.
-
int Rear() Gets the last item from the queue. If the queue is empty, return -1.
-
boolean enQueue(int value) Inserts an element into the circular queue. Return true if the operation is successful.
-
boolean deQueue() Deletes an element from the circular queue. Return true if the operation is successful.
-
boolean isEmpty() Checks whether the circular queue is empty or not.
-
boolean isFull() Checks whether the circular queue is full or not.
You must solve the problem without using the built-in queue data structure in your programming language.
Example:
Input
["MyCircularQueue", "enQueue", "enQueue", "enQueue", "enQueue", "Rear", "isFull", "deQueue", "enQueue", "Rear"]
[[3], [1], [2], [3], [4], [], [], [], [4], []]
Output
[null, true, true, true, false, 3, true, true, true, 4]
Explanation
MyCircularQueue myCircularQueue = new MyCircularQueue(3);
myCircularQueue.enQueue(1); // return True
myCircularQueue.enQueue(2); // return True
myCircularQueue.enQueue(3); // return True
myCircularQueue.enQueue(4); // return False
myCircularQueue.Rear(); // return 3
myCircularQueue.isFull(); // return True
myCircularQueue.deQueue(); // return True
myCircularQueue.enQueue(4); // return True
myCircularQueue.Rear(); // return 4
Constraints:
- 1 <= k <= 1000
- 0 <= value <= 1000
- At most
3000calls will be made toenQueue,deQueue,Front,Rear,isEmpty, andisFull.
思考线
解题思路
这道题要我们设计一个循环队列的类,要实现循环队列,我们需要实现其中的每一个方法。
我们可以在 MyCircularQueue中设置一些属性来保存我们的队列,我们知道需要实现Front\Rear\enQueue\deQueue\isEmpty\isFull这些方法。于是我们可以设置4个变量来记录我们的队列
queue: 这个保存我们的队列head: 这个保存队列的头部size: 保存队列的长度len: 保存队列的最大长度
而我们在构造类的时候,让队列queue初始化,同时初始化head = 0, size =0, len = k
而在我们的所有方法中isEmpty 和isFull比较容易实现我们只需要判断size的值是否是0或者len即可。
而接下来比较容易做的是获取到头部Front,我们只需要判断
- 若队列是空的,返回
-1 - 若队列不为空,返回
head下标对应的值
既然实现了Front,那么Rear也是一样的。我们只要判定队列不为空的情况下,找到尾部的下标即可。
而如何实现这个下标的查找呢?
我们知道在不超过len的情况下,尾部的下标为head + size -1.
我们可以分为两种情况
- 当
(head + size -1)< len的情况,我们的下标就是head +size -1 - 当
(head + size -1)>= len的情况, 我们可以余上len,这样就能得到正确的下标值了。
而我们知道,任意小于len的数被len求余,结果还是其本身,所以,我们就可以把两种情况合二为一变为:
lastInd = (head + size -1)%len
接下来我们再考虑deQueue这个函数如何实现。
我们要从队列中删除一个元素,也就是从头部删除。我们可以通过让head++的方式直接把头部移到下一位即可。在这里值得注意的是,如果此时的head === len -1说明我们已经到了数组的尾部,我们此时需要把head指向下标为0的位置。
最后我们实现一下enQueue方法
这个方法和Rear方法类似, 我最开始思路是,只是若找到的lastInd若和len -1相等,说明到达了最后,我们下一个元素要添加到数组下标为0的位置。否则我们只需要在下标为lastInd + 1的位置放入我们的值即可。
enQueue(value: number): boolean {
if (this.isFull()) {
return false
}
if ((this.head + this.size - 1) % (this.len) !== this.len - 1) {
this.queue[(this.head + this.size - 1) % (this.len) + 1] = value;
} else {
this.queue[0] = value;
}
this.size++
return true;
}
而后面再看这道题的时候,我发现我想的有点多余了,其实这一步我们只要在 head + size -1的基础上 执行+1操作,然后余上len就可以得到对应的放入元素的下标。
通过以上的分析,我们知道了每一个方法的实现想法
最后附上代码如下:
class MyCircularQueue {
queue: number[] // 队列
head: number // 头部位置的数组下标
size: number // 队列的长度
len: number; // 队列能放置的最大长度
constructor(k: number) {
this.queue = new Array(k);
this.head = 0;
this.size = 0;
this.len = k;
}
enQueue(value: number): boolean {
if (this.isFull()) {
return false
}
// 找到最后的位置并+1,得到插入位置
this.queue[(this.head + this.size) % (this.len)] = value;
this.size++
return true;
}
deQueue(): boolean {
if (this.isEmpty()) {
return false
}
if (this.head === this.len - 1) { // 分类讨论 head的位置
this.head = 0;
} else {
this.head++;
}
this.size--
return true;
}
Front(): number {
if (this.isEmpty()) return -1;
return this.queue[this.head];
}
Rear(): number {
if (this.isEmpty()) return -1;
// 因为是实现循环队列,所以我们对结果进行以 len 为基数取模
return this.queue[(this.head + this.size - 1 + this.len)% (this.len)];
}
isEmpty(): boolean {
return this.size === 0;
}
isFull(): boolean {
return this.size === this.len;
}
}
/**
* Your MyCircularQueue object will be instantiated and called as such:
* var obj = new MyCircularQueue(k)
* var param_1 = obj.enQueue(value)
* var param_2 = obj.deQueue()
* var param_3 = obj.Front()
* var param_4 = obj.Rear()
* var param_5 = obj.isEmpty()
* var param_6 = obj.isFull()
*/
时间复杂度 O(1): 在上面的数据结构中,所有的方法都有恒定的时间复杂度。
这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。