【刷题之路Ⅱ】LeetCode 622. 设计循环队列

news2025/5/15 21:35:37

LeetCode 622. 设计循环队列

  • 一、题目描述
  • 二、解题
    • 1、方案1——数组实现,预留一个空判满
      • 1.1、成环思路
      • 1.2、初始化接口
      • 1.3、入队接口
      • 1.4、出队接口
      • 1.5、取队头接口
      • 1.6、取队尾接口
      • 1.7、判空接口
      • 1.8、判满接口
      • 1.9、释放接口
    • 2、方案2——单向循环链表实现,计数器判满
      • 2.2、初始化接口
      • 2.3、入队接口
      • 2.4、出队接口
      • 2.5、取队头接口
      • 2.6、取队尾接口
      • 2.7、判空接口
      • 2.8、判满接口
      • 2.9、释放接口

一、题目描述

原题连接: 622. 设计循环队列
题目描述:
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

  1. MyCircularQueue(k): 构造器,设置队列长度为 k 。
  2. Front: 从队首获取元素。如果队列为空,返回 -1 。
  3. Rear: 获取队尾元素。如果队列为空,返回 -1 。
  4. enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
  5. deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
  6. isEmpty(): 检查循环队列是否为空。
  7. 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

提示:

  1. 所有的值都在 0 至 1000 的范围内;
  2. 操作数将在 1 至 1000 的范围内;
  3. 请不要使用内置的队列库。

二、解题

1、方案1——数组实现,预留一个空判满

1.1、成环思路

我们知道,数组是一块连续的空间:
在这里插入图片描述
当然首位不相连,那么我们怎样才能让它成环呢?
其实我们可以灵活地运用对下标取模的方法来实现下标循环,因为如果下标超出了当前的最大下标,那么取模时候下标就会回归到前面的下标了。
例如,这里如果下标增长到了6:
在这里插入图片描述
那么取模数组的长度6之后,就变成了0,也就轮回了:
在这里插入图片描述
所以这就是我们要将数组设计成环的思路,其实就是通过下标的轮回实现循环访问。

所以我们的实现思路也基本出来了,我们使用两个指针:队头指针front和队尾指针rear来标识队头和队尾,起初我们让们都指向下标为0处:
在这里插入图片描述
然后后面如果我们要执行入队操作的话,先将数据放入rear指向的空间,再让rear向后走一步:
在这里插入图片描述
所以rear其实指向的是队尾元素的下一个元素。
而出队操作就很简单了,直接让front完后走一步就行了,比如我们现在要将第一个元素出队:
在这里插入图片描述
我们并不需要将原来的元素删除,因为这样设计的空间是复用的,后面在入队的元素,会将原来的元素给覆盖掉。

而且最后一定要记得在front和rear完后走后在对他们进行取余数组的长度,因为要成环嘛。

但是这里要事先说明一下,如果我们想要实现长度为k的环形链表,实际上我们是需要开辟k + 1个数组空间的,因为如果不多预留一个空间的话,判满就会变得很麻烦:
在这里插入图片描述
如图,因为我们设计的rear指向的队尾元素的下一个元素,所以如果我们之开辟k个空间的话,那么当队列为满的时候就是front和rear同时指向一个元素。
而我们知道当队列为空的时候,也是fornt和rear同时指向一个元素,这就矛盾了。
所以我们要在k空间的基础之上再开辟一个空间:
在这里插入图片描述
这样当rear指向的是front的前一个元素时候,队列就为满了。
而因为我们这里的坐标已经设计成循环的了,所以是的判断公式应该是:

(rear + 1) % (k + 1) == front % (k + 1)

好啦,这就是数组环形队列实现的主要思路了,接下来就可以来实现了。

1.2、初始化接口

我们先将初始化工作完成:

typedef struct {
    int *data;
    int front;
    int rear;
    int count; // 队列的实际元素个数
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue *queue = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if (NULL == queue) {
        perror("malloc fail!\n");
        exit(-1);
    }
    queue->data = (int*)malloc((k + 1) * sizeof(int));
    queue->front = 0;
    queue->rear = 0;
    queue->count = k;
    return queue;
}

1.3、入队接口

在入队之前,我们先要判断队列是否已满,如果已满就直接返回false,如果没满,就执行上面说到过的思路,最后返回true。

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

1.4、出队接口

出队也是一样,如果队列为空就直接返回false,否则同样执行上面所提到的思路。

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

1.5、取队头接口

这个其实也没什么好说的,为空就返回-1,不为空就直接返回front所指向的元素。

int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) {
        return -1;
    }
    return obj->data[obj->front];
}

1.6、取队尾接口

而取队尾这个接口就不同了,因为我们设计的rear是指向队尾元素的下一个元素,所以我么其实要返回的是rear的前一个位置的元素。
一般情况下,我们直接返回rear - 1处的元素即可:
在这里插入图片描述
但是还有一个特殊情况就是当rear为0的时候:
在这里插入图片描述
因为rear - 1 = -1,所以我们此时并不能直接用rear- 1来访问数组。
那要怎么办呢?
其实很简单,我们可以使用(rear - 1) + (k + 1)对k + 1取余,正常情况下rear - 1 > 0的时候,加上一个k + 1在对k + 1进行取余其实对结果没什么影响,而当rear - 1= -1时候,加法也就变成了减法,所以就变成了k对k + 1取余,其结果是k,正好是最后一个下标。

int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) {
        return -1;
    }
    int RearIndex = (obj->count + obj->rear) % (obj->count + 1);
    return obj->data[RearIndex];
}

1.7、判空接口

对于这个接口,我们直接返回rear和fornt是否相等的结果即可:

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

1.8、判满接口

这个接口的思路其实上面就已经讲过:

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

1.9、释放接口

因为数组也是动态开辟出来的,所以再释放队列之前要先将数组给释放了。

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->data);
    obj->front = 0;
    obj->rear = 0;
    obj->count = 0;
    free(obj);
}

2、方案2——单向循环链表实现,计数器判满

可能大家在看到“环形队列”这几个字的时候都会想到要用环形链表来实现。因为环形链表本来就已经成环了,而且数组一样老是需要对下标进行取模,判满也比较简单:
在这里插入图片描述

我尝试过之后,确实觉得是比用数组实现的逻辑要简单一点。
而在这个方案中我们就换一种方法来判满,在队列中设计一个计数器变量size,然后每次入队或出队时相应的让size自增1或自减1即可,当size为0时候说明队列为空,当size等于k的时候就说明队列已满。
这样就不需要在预留一个节点了。

2.2、初始化接口

我们首先要把初始化工作做完,使用链表实现比起数组来比较复杂的就是在初始化时候要将节点开辟出来,并连接成环,这个操作其实比数组的直接开辟要复杂一点儿:

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue *queue = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if (NULL == queue) {
        perror("malloc fail!\n");
        exit(-1);
    }
    ListNode *head = NULL; // 保存第一个节点
    ListNode *tail = NULL; // 保存最后一个节点
    int i = 0;
    for (i = 0; i < k; i++) {
        ListNode *node = (ListNode*)malloc(sizeof(ListNode));
        if (NULL == node) {
            perror("malloc fail!\n");
            exit(-1);
        }
        if (0 == i) {
            head = node;
            tail = node;
            queue->front = head;
            queue->rear = head;
        } else {
            tail->next = node; // tail连接上后面的节点
            tail = tail->next; // tail完后走
        }
    }
    // 连接成环
    tail->next = head;
    queue->size = 0;
    queue->count = k;
    return queue;
}

2.3、入队接口

入队的话,我们这里还是选择让队尾指针rear指向队尾的下一个节点,所以我们这里要向将当前rear指向的节点赋上新的值,然后再让rear往后走一步。

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj)) {
        return false;
    }
    obj->rear->val = value;
    obj->rear = obj->rear->next;
    obj->size++;
    return true;
}

2.4、出队接口

因为我们是通过给节点的数据域赋上新值,来表示加入新数据的,所以我们这里也并不需要将原来的节点删除,直接让front指针往后走一步即可:

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) {
        return false;
    }
    obj->front = obj->front->next;
    obj->size--;
    return true;
}

2.5、取队头接口

这个接口其实也没什么好说的,如果队列为空就直接返回-1,不为空就直接返回队头节点的值。

int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) {
        return -1;
    }
    return obj->front->val;
}

2.6、取队尾接口

因为我们设计的rear指向的是队尾节点的下一个节点,所以我们其实要返回的是rear的前一个节点。但又因为单链表的局限性,我们不能直接找到rear的前驱。所以我们这个接口其实有两种方案,一种是可以再队列中在增加一个成员rearPre,来记录rear的前一个节点;另一种是在这个接口内直接重头遍历找到rear的前一个。
因为我觉得再定义一个rearPre没有什么必要,因为只有在取队尾的接口中用到,定义了好型显得有点儿多余,所以我这里选择的是第二种方案——直接遍历,这其实就和单链表的遍历一样了:

int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) {
        return -1;
    }
    ListNode *cur = obj->front;
    while (cur->next != obj->rear) {
        cur = cur->next;
    }
    return cur->val;
}

2.7、判空接口

我们直接返回size是否等于0的判断结果即可:

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->size == 0;
}

2.8、判满接口

直接返回size是否等于k的结果即可:

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return obj->size == obj->count;
}

2.9、释放接口

因为单链表的节点也是动态开辟出来的,所以我们在释放队列之前要先将这些节点也释放了:

void myCircularQueueFree(MyCircularQueue* obj) {
    ListNode *cur = obj->front->next;
    obj->front->next = NULL;
    while (cur) {
        ListNode *next = cur->next;
        free(cur);
        cur = next;
    }
    free(obj);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot集成Oracle实战和坑

这里写目录标题 前言1.导包2. 配置文件&#xff1a;数据库信息辟谣 3.代码 问题更新 前言 前段时间搞了一个oracle的项目&#xff0c;耗费了很多时间&#xff0c;现在项目整体上线了&#xff0c;在此记录下实战过程以及遇到的坑&#xff0c;有需要的网友也可以直接拿去使用。 …

文本三剑客正则表达式1

文章目录 文本三剑客&正则表达式11 sort1.1 sort -f1.2 sort -b1.3 sort -n1.4 sort -r1.5 sort -u1.6 sort -t1.7 sort -k1.8 sort -o 2 uniq2.1 uniq -c2.2 uniq -u2.3 uniq -d 3 tr3.1tr -c3.2 tr -d3.3 tr -s :3.4 tr -t 4 cut4.1 cut -d4.2 cut -f4.3 cut -b4.4 cut -…

基于 SpringBoot+Vue 的家政服务管理平台

1. 背景 本系统主要是设计出家政服务管理平台&#xff0c;基于B/S构架&#xff0c;后台数据库采用了Mysql&#xff0c;可以使数据的查询和存储变得更加有效&#xff0c;可以确保家政服务管理的工作能够正常、高效的进行&#xff0c;从而提高工作的效率。总体的研究内容如下&am…

模组uart调试总结

配置驱动选项 1.1 首先通过原理图确定其串口号&#xff0c;比如UART1、UART3_HS&#xff0c;同时查看该串口引脚是否有复用功能&#xff0c;比如用作SIM卡引脚。如果有复用&#xff0c;需要在设备树配置中取消复用功能的选项&#xff0c;然后选中串口功能&#xff0c;高通平台设…

【嵌入式环境下linux内核及驱动学习笔记-(12-设备树操作函数)】

目录 1、设备树对应的数据结构1.1 struct device_node1.2 struct property 2、设备树操作函数2.1 查找字点的函数2.1.1 of_find_node_by_path2.1.2 of_find_node_by_name2.1.3 of_find_node_by_type2.1.4 of_find_compatible_node2.1.5 of_find_node_by_phandle2.1.6 of_get_ch…

Automa爬取网页数据直接入库(四)

介绍 在使用automa浏览器插件爬取数据时,可以直接通过发送请求将爬取到的网页数据持久化到数据库中 本次以360趋势图爬取后插入数据库当做测试 建立流程 首先建立打开360趋势图的流程,这个不再演示,直接从获取分析元素开始 打开要爬取的网页 点击定位元素 建立表格存储爬取…

【shell脚本】免交互操作

免交互操作 一、Here Document免交互1.1免交互概述1.2语法格式1.3实验 二、Expect2.1脚本格式2.2实验 一、Here Document免交互 1.1免交互概述 使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp、cat 或 read 命令。是标准输入的一种替代品可以帮助…

森海塞尔及诺音曼携重磅新品亮相2023广州国际专业灯光、音响展览会

森海塞尔及诺音曼携重磅新品亮相2023广州国际专业灯光、音响展览会 以卓越产品和创新技术引领专业音频行业发展 广州&#xff0c;2023年5月16日——森海塞尔和诺音曼将于2023年5月22日至25日&#xff0c;携重磅新品及全新音频技术亮相第21届广州国际专业灯光、音响展览会。森海…

十七、Bus消息总线

目录 1、Bus消息总线介绍&#xff1a; 2、使用消息总线实现配置自动更新 2.1、方案一架构图&#xff1a; 2.2、方案二架构图&#xff08;常用&#xff09; 3、对springcloud-config-server项目进行改造 3.1、修改springcloud-config-server项目的pom文件&#xff0c;添加…

如何防范鱼叉式网络钓鱼及其他钓鱼攻击

在当今日益互联的世界中&#xff0c;远程访问已成为许多组织允许员工随时随地办公的必要条件。远程访问是一把双刃剑&#xff0c;有自身的优势&#xff0c;但也带来了重大的安全风险。网络犯罪分子一直想方设法利用远程访问系统的漏洞&#xff0c;试图通过这些漏洞发起鱼叉式网…

软件测试行业对新人友好吗?

软件测试真的算是对新人小白非常友好的学科了&#xff0c;但是你也千万不要抱有幻想&#xff0c;觉得轻轻松松就能掌握真正的技能&#xff0c;然后如愿找到高薪工作。从0到1还是需要一个过程的&#xff0c;也是需要你付出相当大的努力去学习的 随着人工智能时代的到来&#xf…

第01讲:RocketMQ入门

一、什么是消息队列 ​ 消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题。实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。目前在生产环境&#x…

【C++进阶3-二叉搜索树】强,但没貌似还不够?

今天&#xff0c;带来二叉搜索树的讲解。 文中不足错漏之处望请斧正&#xff01; 是什么 二叉搜索树&#xff08;Binary Search Tree&#xff09;又称二叉排序树。 它可以是一棵空树&#xff0c;也可以是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所…

《花雕学AI》不用花钱,也能和ChatGPT聊天!快来看看这五个免费客户端吧

引言 你有没有想过和一个智能的聊天机器人对话&#xff1f;一个可以陪你聊天、讲故事、写代码、模仿名人、生成歌词等等的聊天机器人&#xff1f;如果你有这样的想法&#xff0c;那么你一定要了解ChatGPT。ChatGPT是一个由OpenAI开发的人工智能聊天机器人程序&#xff0c;它使用…

基于Ajax+JSon的表格数据浏览【简单版--没连接数据库】+【连接数据库版】

目录 基于AjaxJSon的表格数据浏览【简单版--没连接数据库】 代码&#xff1a; ajax.js ch10_4.jsp student.java Query.java 运行结果&#xff1a; 点击获取表格后&#xff1a; 基于AjaxJSon的表格数据浏览【简单版--没连接数据库】 代码&#xff1a; ajax.js //声明XM…

【案例实战】SpringBoot3.x自定义封装starter实战

1.starter背景简介及作用 &#xff08;1&#xff09;什么是starter starter是SpringBoot中的一个新发明&#xff0c;它有效的下降了项目开发过程的复杂程度&#xff0c;对于简化开发操做有着很是好的效果。 starter的理念&#xff1a;starter会把全部用到的依赖都给包含进来&a…

三极管的几点应用

三极管有三个工作状态&#xff1a;截止、放大、饱和&#xff0c;放大状态很有学问也很复杂&#xff0c;多用于集成芯片&#xff0c;比如运放&#xff0c;现在不讨论。其实&#xff0c;对信号的放大&#xff0c;我们通常用运放处理&#xff0c;三极管更多的是当做一个开关管来使…

微信小程序入门05-用户登录注册接口开发

用户登录注册&#xff0c;我们先需要开发后端的接口&#xff0c;接口一般需要有入参&#xff0c;然后和数据库进行交互。 1 创建表 我们现在先实现用户的登录及注册&#xff0c;建表语句 create database diancan; use diancan; CREATE TABLE users (id INT AUTO_INCREMENT …

软件设计模式介绍与入门

目录 1、软件设计模式的起源 2、什么是设计模式&#xff1f; 2.1、设计模式的设计意图 2.2、设计模式的分类准则 3、为什么要学习设计模式 4、如何学习设计模式 5、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#x…

毕业论文写作技巧

毕业论文的组成部分目录自定义目录 摘要&#xff08;Abstract&#xff09;绪论相关工作&#xff08;Related work&#xff09;研究方法和结果&#xff08;Method and Results&#xff09;研究方法研究结果 结论&#xff08;Conclusion&#xff09; 写好一篇论文其实就是讲好一个…