L33.【LeetCode笔记】循环队列(数组解法)
目录
1.题目
2.分析
方法1:链表
尝试使用单向循环链表模拟
插入节点
解决方法1:开辟(k+1)个节点
解决方法2:使用变量size记录队列元素个数
获取队尾元素
其他函数的实现说明
方法2:数组
重要点:指针越界的解决方法
方法1:单独判断
方法2:取模
3.数组代码的逐步实现
方法1:越界时单独判断
提交结果
方法2:每次都取模
提交结果
1.题目
https://leetcode.cn/problems/design-circular-queue/
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 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提示:
- 所有的值都在 0 至 1000 的范围内;
- 操作数将在 1 至 1000 的范围内;
- 请不要使用内置的队列库。
2.分析
方法1:链表
看到本题,很容易想到用链表解
尝试使用单向循环链表模拟
例如k==3,很容易想到开辟三个空节点
要访问循环队列的头和尾,需要两个指针front(头指针)和rear(尾指针),初始状态下,链表为空时,front和rear都指向同一个节点
现检测链表的功能是否能正常执行
插入节点
每插入一个元素,按队列"先进先出"原则,应该rear++,如下图:
当队列满时,如下图:
发现问题:队列为空或满时,front和rear都指向同一个节点,无法区分
解决方法1:开辟(k+1)个节点
增加一个节点后,当队列满时,rear->next==front;当队列为空时,rear==front
解决方法2:使用变量size记录队列元素个数
当队列为空时,size==0;当队列满时,size==k.以此来区分两种情况
获取队尾元素
无论开辟(k+1)个节点还是使用变量size记录队列元素个数,rear都指向尾节点的下一个节点,需要找尾(时间复杂度为,较消耗时间),或者另外使用一个rear_prev变量来记录rear指向节点的前一个节点,或者使用双向链表
其他函数的实现说明
从队首获取元素:返回front指向的节点的值即可
删除一个元素:front++
方法2:数组
讲讲和链表的不同点:
重要点:指针越界的解决方法
1.无论开辟(k+1)个节点还是使用变量size记录队列元素个数,front和rear都有越界的可能性
以开辟(k+1)个节点举例:
例如下图,再插入一个元素,如果rear++,则会越界
例如下图,再删除一个元素,如果front++,则会越界
2.获取队尾元素时,可能出现rear越界访问的情况
当队列不为空时,直接访问return obj->arr[obj->rear-1];可能访问到obj->arr[-1]的位置,可以单独判断或者对rear取模
方法1:单独判断
如果rear+1越界,则rear=0;如果front+1越界,则front=0;
方法2:取模
队列未满时,插入元素:当rear==k时,再插入元素时rear++,之后处理越界的rear,使其等于0,则,rear==(rear+1)%(k+1)
队列不为空时,删除元素:当front==k时,再插入元素时front++,之后处理越界的front,使其等于0,则,front==(front+1)%(k+1)
队列不为空时,访问队尾元素:例如rear==0,要访问到rear==k的位置,即arr[(rear-1+k+1)%(k+1)==arr[(rear+k)%(k+1)
判断队列是否已满,如果满了,例如rear==k,要使rear的下一个为front==0,即判断(rear+1)%(k+1)==front
3.数组代码的逐步实现
读题可知:如果用数组来模拟循环队列,那么结构体成员变量应该有4个
1.指向数组的指针a,类型为int*
2.队首和队尾各需要一个变量来访问:front和rear,类型都为int,充当数组a的下标
3.队列的长度k,类型为int
显然应该用malloc在堆区上开辟空间,接收的指针为结构体指针,否则函数返回后,栈区空间会被销毁
之后初始化结构体的成员变量
方法1:越界时单独判断
typedef struct
{
int* arr;
int front;
int rear;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr=(int*)malloc(sizeof(int)*(k+1));
obj->front=obj->rear=0;
obj->k=k;
return obj;
}
bool myCircularQueueIsFull(MyCircularQueue* obj);//先声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);//先声明
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if (myCircularQueueIsFull(obj))
return false;
if (obj->rear!=obj->k)
obj->arr[obj->rear++]=value;
else
obj->rear=0;
obj->arr[obj->k]=value;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if (myCircularQueueIsEmpty(obj))
return false;
if (obj->front!=obj->k)
obj->front++;
else
obj->front=0;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj)
{
if (myCircularQueueIsEmpty(obj))
return -1;
return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj)
{
if (myCircularQueueIsEmpty(obj))
return -1;
if (obj->rear)//rear!=0
return obj->arr[obj->rear-1];
//rear==0
return obj->arr[obj->k];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->front==obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
int rear_copy=obj->rear;
if (rear_copy+1==obj->front)
return true;
else if (rear_copy==obj->k && obj->front==0)
return true;
else
return false;
}
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->arr);
free(obj);
}
代码还可以精简些,例如
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->rear+1==obj->front) || (obj->rear==obj->k && obj->front==0);
}
提交结果
方法2:每次都取模
★注意:rear++或者front++后一定要取模!
typedef struct
{
int* arr;
int front;
int rear;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr=(int*)malloc(sizeof(int)*(k+1));
obj->front=obj->rear=0;
obj->k=k;
return obj;
}
bool myCircularQueueIsFull(MyCircularQueue* obj);//先声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);//先声明
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if (myCircularQueueIsFull(obj))
return false;
obj->arr[obj->rear++]=value;
obj->rear%=obj->k+1;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if (myCircularQueueIsEmpty(obj))
return false;
obj->front++;
obj->front%=(obj->k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj)
{
if (myCircularQueueIsEmpty(obj))
return -1;
return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj)
{
if (myCircularQueueIsEmpty(obj))
return -1;
return obj->arr[(obj->rear-1+obj->k+1)%(obj->k+1)];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->front==obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->rear+1)%(obj->k+1)==obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->arr);
free(obj);
}
提交结果
注:有关本题的链表解法参见下一篇