LeetCode 622. 设计循环队列
题目描述


题意分析
实现容量为
k的先进先出队列,支持入队、出队、读取两端和判断空满。把数组首尾视为相连,下标走过末尾后回到开头,就能复用出队释放的空间,各项操作都无需搬移已有元素。
解法:数组 + 队首下标 + 元素数量
核心思路
[!blue]
用
head记录队首下标,size记录有效元素数量,始终满足0 <= size <= k。从队首起,第i个元素的数组下标为(head + i) % k,其中i从 $0$ 到size - 1。这段环形位置按顺序组成当前队列。非空时,当前队尾的偏移量是
size - 1,下标为(head + size - 1) % k;下一次入队的偏移量为size,下标就是(head + size) % k。先判满再写入,才能保证新位置不属于仍在队列中的元素。出队时让
head向后移动一格并取模,再将size减一,剩余元素的先后顺序保持不变。旧队首的值可以留在数组里,因为只有从新head开始的size个位置有效,后续入队会在需要时覆盖旧值。
size == 0表示空,size == k表示满。这两种状态下,按入队公式算出的下标都等于head,所以要由数量区分空满。容量为 $1$ 时下标始终为 $0$,仍可用size正确判断空满。
解题步骤
- 构造时申请长度为
k的数组,令head = 0、size = 0。- 入队前判满;将值写入
(head + size) % k,再执行size++。- 出队前判空;令
head = (head + 1) % k,再执行size--。旧值无需清除,因为有效性只由head和size决定。- 非空时,队首为
data[head],队尾为data[(head + size - 1) % k];空队列按题意返回-1。- 判空、判满分别比较
size与 $0$、k。满时入队、空时出队都返回false,且不能修改数组或下标、数量。
代码实现
class MyCircularQueue {
private int[] data;
private int head;
private int size;
public MyCircularQueue(int k) {
data = new int[k];
}
public boolean enQueue(int value) {
if (isFull()) {
return false;
}
// 当前队首加有效数量得到下一次写入位置,随后按容量回绕。
int tail = (head + size) % data.length;
data[tail] = value;
size++;
return true;
}
public boolean deQueue() {
if (isEmpty()) {
return false;
}
// 出队只需要移动队首下标,不需要清空数组位置。
head = (head + 1) % data.length;
size--;
return true;
}
public int Front() {
return isEmpty() ? -1 : data[head];
}
public int Rear() {
if (isEmpty()) {
return -1;
}
// 队尾是最后一个有效元素,比下一次写入位置少一格。
return data[(head + size - 1) % data.length];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == data.length;
}
}
type MyCircularQueue struct {
data []int
head int
size int
}
func Constructor(k int) MyCircularQueue {
return MyCircularQueue{data: make([]int, k)}
}
func (this *MyCircularQueue) EnQueue(value int) bool {
if this.IsFull() {
return false
}
// 当前队首加有效数量得到下一次写入位置,随后按容量回绕。
tail := (this.head + this.size) % len(this.data)
this.data[tail] = value
this.size++
return true
}
func (this *MyCircularQueue) DeQueue() bool {
if this.IsEmpty() {
return false
}
// head 按容量取模前进,实现循环复用数组。
this.head = (this.head + 1) % len(this.data)
this.size--
return true
}
func (this *MyCircularQueue) Front() int {
if this.IsEmpty() {
return -1
}
return this.data[this.head]
}
func (this *MyCircularQueue) Rear() int {
if this.IsEmpty() {
return -1
}
// 队尾是最后一个有效元素,比下一次写入位置少一格。
return this.data[(this.head+this.size-1)%len(this.data)]
}
func (this *MyCircularQueue) IsEmpty() bool {
return this.size == 0
}
func (this *MyCircularQueue) IsFull() bool {
return this.size == len(this.data)
}
复杂度分析
- 时间复杂度:构造为 $O(k)$,初始化容量数组;后续各项操作为 $O(1)$,不搬移元素。
- 空间复杂度:$O(k)$,用于保存容量为
k的定长数组。
关键点总结
[!green]
- 先定义状态不变量,再由它推导入队位置、队尾位置和空满条件,比背公式可靠。
- 取模负责回绕,
size负责区分空与满,两者职责不同。head + size指向下一个写入位置,所以队尾必须再减 1。- 出队只改变逻辑边界,不必清除数组中的旧值。
易错点总结
[!yellow]
- 只用
head == tail同时判断空和满,两种状态会产生歧义;当前实现由size区分。- 入队前漏判满会覆盖仍有效的队首元素,并破坏
size <= k。- 队尾公式漏掉
-1,读到的是下一个待写位置;公式漏掉head,绕圈后会读错。- 下标前进忘记取模,走过数组末尾就会越界。
- 空队列调用
Front或Rear必须返回-1,不能直接读取数组默认值。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 641. 设计循环双端队列 | 中等 | 本题只支持队尾入、队首出,原题循环双端队列还支持两端插入删除。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!