【初阶数据结构和算法】leetcode刷题之设计循环队列

【初阶数据结构和算法】leetcode刷题之设计循环队列
在这里插入图片描述


文章目录

一、实现循环队列

题目链接:https://leetcode.cn/problems/design-circular-queue/description/

1.大致思路分析

我们来看看题目描述:

在这里插入图片描述


这是leetcode上的一道中等难度的题,一般来说,leetcode上简单的题不一定简单,中等一定很麻烦,这道题虽然核心也不难,但是就是麻烦
在做这道题之前,我们要先来介绍一下什么是循环队列,循环队列是一种特殊的队列,它的首尾在逻辑上是相连的,但是还是队头出数据,队尾入数据,保持队列先进先出的特性,被称为环形缓冲器
但是它和我们之前实现的队列的最大不同是,之前我们实现的队列如果容量不够是可以扩容的,是可变的,而循环队列的容量确定之后就不能再更改,比如后面我们写初始化方法时,会给定一个值给我们初始化,然后我们开辟好空间后就不再修改它的大小了,所以循环队列的容量一经确定就不会再更改
那么我们接下来大致来分析一下循环队列的结构,我们是继续使用实现队列时的链表,还是说使用数组呢?其实两个都可以,只是使用链表需要使用循环链表,那么哪一个结构更优呢?
循环队列和普通队列的一个较大区别是,循环队列的大小是固定好了的,当我们去删除数据时,不会直接释放空间,而是会留着存储以后新插入的数据,那么很明显数组的优势更大一些
因为数组数据的删除可以只改变size,比如尾删就是让size- -,这样就间接实现了删除,我们访问不到原本的尾数据了,但是其实那个空间还在,只是我们size- -后访问不到了而已
但是链表数据的删除会释放节点,如果不释放节点的话让它强行留下来就会很麻烦,但是我们循环队列的大小又是固定的,到时候重新申请节点插入到原位置又很麻烦,并且链表的开销比数组要大,所以综上我们实现循环链表最好使用数组
由于队列要遵循头删尾入,所以我们最好像定义队列一样,定义两个下标front和rear分别指向循环队列的头和尾,方便进行操作,注意:rear和之前顺序表的size一样,不是指向有效元素中的最后一个元素,而是这个最后一个有效元素的下一个位置
那么数组怎么实现首尾相连呢?首尾相连就是尾的下一个节点是头,实现这个需要一定的技巧性,要使用模运算,当rear或者front越界时,模上容量大小就可以让它们重新回到开头,就像首尾相连了一样,如图:

在这里插入图片描述

现在我们大致知道了循环队列的底层存储,接下来就边做题边讲解思路

2.循环队列的结构定义和初始化

结构定义

现在我们知道了使用数组,所以循环队列中肯定要有一个数组,但是我们不知道数组的具体大小,需要到时候根据初始化函数的参数确定,所以我们这里直接用一个指针代替,到时候动态申请空间
由于队列要遵循头删尾入,所以我们最好像定义队列一样,定义两个下标front和rear分别指向循环队列的头和尾(这个尾相当于顺序表中的size),方便进行操作,还有就是我们要知道申请了多大空间,所以用一个整型变量capacity来存储
那么是否要头删使用数组的效率会很低呢?这个是不需要担心的,因为循环队列的空间不需要释放,所以头删只需要让front++即可,到后面出队列再细讲,那么最后循环队列的大致结构定义如下:

typedefstruct{int* arr;int capacity;int rear;int front;} MyCircularQueue;

初始化

在初始化函数中给了我们一个参数k,就是我们要申请的空间大小,我们直接通过动态申请k个整型(后面可能会需要更改,我们先实现到这里),然后将容量置为k,两个指向头和尾的下标置为0,如下:

MyCircularQueue*myCircularQueueCreate(int k){ MyCircularQueue* pq =(MyCircularQueue*)malloc(sizeof(MyCircularQueue));if(pq ==NULL){perror("malloc");returnNULL;}//后面这里可能会进行改动,这里做一个标记 pq->arr =(int*)malloc(sizeof(int)* k);if(pq->arr ==NULL){perror("malloc");returnNULL;} pq->rear = pq->front =0; pq->capacity = k;return pq;}

3.循环队列的判空和判满

判空和判满难点分析

循环队列的难点不是后面的入队列和出队列,而是这个部分的判空和判满,因为我们会发现队列空时front和rear相等,队列满时front和rear也相等了,它们难以区分了,如图:

在这里插入图片描述


可以看到队列空时,front和rear相等,指向头部,那么如果队列满了呢?如图:

在这里插入图片描述


这里的rear相当于是之前顺序表中的size,指向有效数据的下一个位置,那么现在队列满了,rear指向元素5的下一个位置,同时元素5是最后一个元素,所以根据循环首尾相连的特点,它需要指向头
那么怎么让它回到头呢?采用的方法就是让rear%capacity,这个方法可以自动帮我们去将越界的rear放回到头,但是问题就来了,此时front和rear又相等了,那么就说明front和rear相等可能是队列为空,也可能是队列满了
怎么解决呢?我们这样做,在开辟空间时,我们不再开辟k个空间,而是开辟k+1个空间,多开辟一个空间,有什么作用呢?我们画画图就知道了:

在这里插入图片描述


在这里插入图片描述

是不是非常巧妙呢?但是问题又来了,当空间满了之后,front和rear不相等了,我们如何判断队列是否满了呢?
其实也不难,我们多开了一个空间,那么实际的空间就应该是capacity+1,实际rear也要+1,所以判断循环队列空间是否为满的条件就是front等于(rear+1) % (capacity+1),现在我们简单画个图来解释,后面还有其它情况到时候再说,如下:

在这里插入图片描述


那么现在我们分析好了之后,我们还要做一件事,就是之前我们只开辟了k个空间,现在我们要把它改成k+1个空间,其它不变,如下:

 pq->arr =(int*)malloc(sizeof(int)*(k+1));

这个做完之后我们就可以直接来实现判空和判满了

判空

判空的条件就是循环队列的front等于rear,如下:

bool myCircularQueueIsEmpty(MyCircularQueue* obj){return obj->front == obj->rear;}

判满

判满的条件就是循环队列 (rear + 1) % (capacity + 1) == front,如下:

bool myCircularQueueIsFull(MyCircularQueue* obj){return(obj->rear +1)%(obj->capacity +1)== obj->front;}

4.循环队列的入队列和出队列

我们先来看看题目给出入队列和出队列操作的要求,如图:

在这里插入图片描述


题目的意思是如果我们插入或删除成功需要返回true,虽然题目没有说,但是我们应该都能想到插入和失败是不是就要返回false了,所以我们在入队列和出队列之前要记得判断能否插入和删除数据,那么接下来我们进入对队列和出队列的具体分析

入队列

在入队列之前,我们首先要判断循环队列能否入队列,如果它满了,说明我们不能再插入数据了,就要直接返回false
判断完之后我们就要真正来实现入队列操作了,入队列就是往数组的最后插入数据,也就是往rear的位置插入数据,插入完之后让rear++
但是我们要注意一个点,rear++之后可能会越界,我们要让它模上数组真正大小,也就是capacity+1,让它重新走到开头,如图:

在这里插入图片描述


在这里插入图片描述


当然,当我们插入完数据后,不要忘记返回true,如下:

bool myCircularQueueEnQueue(MyCircularQueue* obj,int value){if(myCircularQueueIsFull(obj)){return false;} obj->arr[obj->rear++]= value; obj->rear %= obj->capacity +1;return true;}

出队列

在出队列之前,我们首先要判断循环队列能否出队列,如果它为空,说明我们不能再删除数据了,就要直接返回false
判断完之后我们就要真正来实现出队列操作了,出队列就是删除数组头的数据,但是由于我们不需要释放空间,并且这是数组,所以我们可以直接让front++,间接实现头删,如图:

在这里插入图片描述


但是同样有一个问题,那就是front++后也可能越界,比如上面的例子中,如果再入队列几次,再去出队列,front++,是不是就刚好越界了,所以front也是有可能越界的
为了防止越界,让循环队列首尾相连,也只需要让front % (capacity + 1)即可,跟上面入队列是rear的解决方式一样,这里就不多说了,有了思路之后我们直接来写代码,如下:

bool myCircularQueueDeQueue(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return false;} obj->front++; obj->front %= obj->capacity +1;return true;}

5.循环队列取队头和队尾元素

在实现取队头和队尾元素操作之前,我们来看看题目的要求:

在这里插入图片描述


题目提示我们,如果循环队列为空是不是就不能取到队头和队尾元素了,所以如果我们判断出来循环队列为空,需要直接返回-1

取队头元素

在取队头元素之前我们需要判断循环队列是否为空,为空就返回-1,不为空我们才去取队头元素,方法也很简单,就是直接返回front位置的数据即可,如下:

intmyCircularQueueFront(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return-1;}return obj->arr[obj->front];}

取队尾元素

在取队尾元素之前我们需要判断循环队列是否为空,为空就返回-1,不为空我们才去取队尾元素,但是取队尾元素会麻烦一点,因为rear不是指向最后一个有效数据,而是指向有效数据的下一个位置
那么是不是直接返回rear-1位置的数据就可以了呢?大部分情况下是这样的,但是有一种例外,就是当rear = 0时,如果直接-1的话就变成了-1了,越界了,而它的前一个位置应该是原队列中最后一个元素,如图:

在这里插入图片描述


所以我们可以用重新定义一个prev,让它等于rear-1,如果prev < 0,我们就让它加上循环队列的真实大小(capacity+1),在上图中rear-1为-1,加上真实大小6就变成了5,成功回到了最后一个位置
处理完prev可能小于0的情况后,就可以直接返回数组中prev位置的数据的,如下:

intmyCircularQueueRear(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return-1;}int prev = obj->rear-1;if(prev <0){ prev += obj->capacity +1;}return obj->arr[prev];}

6.循环队列的销毁

循环队列的销毁是最简单的,只要循环队列底层的数组不为空,就将它的空间释放掉,最后释放掉整个循环队列,如下:

voidmyCircularQueueFree(MyCircularQueue* obj){if(obj->arr)free(obj->arr);free(obj); obj =NULL;}

7.最后题解源码

typedefstruct{int* arr;int capacity;int rear;int front;} MyCircularQueue; MyCircularQueue*myCircularQueueCreate(int k){ MyCircularQueue* pq =(MyCircularQueue*)malloc(sizeof(MyCircularQueue));if(pq ==NULL){perror("malloc");returnNULL;} pq->arr =(int*)malloc(sizeof(int)*(k+1));if(pq->arr ==NULL){perror("malloc");returnNULL;} pq->rear = pq->front =0; pq->capacity = k;return pq;} bool myCircularQueueIsEmpty(MyCircularQueue* obj){return obj->front == obj->rear;} bool myCircularQueueIsFull(MyCircularQueue* obj){return(obj->rear +1)%(obj->capacity +1)== obj->front;} bool myCircularQueueEnQueue(MyCircularQueue* obj,int value){if(myCircularQueueIsFull(obj)){return false;} obj->arr[obj->rear++]= value; obj->rear %= obj->capacity +1;return true;} bool myCircularQueueDeQueue(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return false;} obj->front++; obj->front %= obj->capacity +1;return true;}intmyCircularQueueFront(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return-1;}return obj->arr[obj->front];}intmyCircularQueueRear(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return-1;}int prev = obj->rear-1;if(prev <0){ prev += obj->capacity +1;}return obj->arr[prev];}voidmyCircularQueueFree(MyCircularQueue* obj){if(obj->arr)free(obj->arr);free(obj); obj =NULL;}

那么今天的刷题就分享到这里,整个栈和队列的题就暂时分享到这里,有什么疑问欢迎提出,后面我们就进入二叉树的学习啦,敬请期待吧!
bye~

Read more

Flink Batch Shuffle Blocking vs Hybrid 怎么选?Hash vs Sort 怎么调?一篇把坑点讲透的实战文

1. 为什么 Batch Shuffle 跟 Streaming 的 Pipelined Shuffle 不一样? Streaming 的 pipelined shuffle 强依赖:上游和下游同时运行、边产边传边算。这对资源要求更高(slot、网络 buffer、并发等)。 Batch 的 blocking/hybrid 主要目标是: * 允许上下游不同时运行(尤其 blocking):用更少资源把任务跑完 * 通过“持久化中间结果”提升稳定性(失败可重读、可恢复) * 在资源充足时(尤其 hybrid)尽量缩短整体执行时间 一句话:batch shuffle 关注的是 **“资源效率 + 稳定性 + 总耗时”**三角平衡。 2. Blocking

By Ne0inhk
【优选算法必刷100题:专题六】(模拟算法)第039~343题:替换所有的问号、提莫攻击、Z 字形变换、外观数列、数青蛙

【优选算法必刷100题:专题六】(模拟算法)第039~343题:替换所有的问号、提莫攻击、Z 字形变换、外观数列、数青蛙

🎬 个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬 艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 文章目录 * 039 替换所有的问号 * 1.1 解法:模拟的思想 * 1.2 算法实现 * 1.3 博主手记 * 040 提莫攻击 * 2.1 解法:模拟 + 分情况讨论 * 2.2 算法实现 * 2.3 博主手记 * 041 Z 字形变换

By Ne0inhk

Flutter 三方库 in_date_utils 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、高效的日期逻辑处理与万年历算法引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 in_date_utils 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、高效的日期逻辑处理与万年历算法引擎 在鸿蒙(OpenHarmony)系统的日历、任务管理或考勤应用中,如何快速计算某月的天数、判断闰年、或优雅地对日期进行加减操作?in_date_utils 为开发者提供了一套开箱即用的日期增强工具集。本文将深入实战其在鸿蒙生态中的应用。 前言 什么是 in_date_utils?它是 Dart 原生 DateTime 的强力补丁。在 Flutter for OpenHarmony 的实际开发中,我们经常需要处理诸如“上周一的日期”、“本月最后一个周五”等复杂的业务逻辑。利用该库,我们可以避免重复编写琐碎的日期数学运算,让鸿蒙应用的代码更加简洁、易读且稳健。 一、

By Ne0inhk
【优选算法】滑动窗口算法:专题一

【优选算法】滑动窗口算法:专题一

目录 引言:  【209. 长度最小的子数组】 题目描述: 实现核心及思路: 思路可视化: 代码实现: 【无重复字符的最长子串】 题目描述: 实现核心及思路: 思路可视化: 代码实现: 【最大连续1的个数III】 题目描述: 实现核心及思路: 代码实现: 【1658.将x减到0的最小操作数】 题目描述: 实现核心即思路: 代码实现: 引言: 滑动窗口?用两个指针维护一个动态的 “窗口” 区间,通过移动指针来扩大或缩小窗口,在一次遍历中完成计算,时间复杂度通常为 O (n)。 典型应用:寻找最长无重复字符的子串找到和为目标值的最短子数组字符串的排列匹配 一般步骤(模板): (1)定义left 和 right 指针同时指向数组首元素; (2)当符合要求时,right++,模拟进窗口; (3)不满足要求时,left++,模拟出窗口; (4)

By Ne0inhk