【刷题之路】LeetCode 225. 用队列实现栈

news2024/11/26 8:36:01

【刷题之路】LeetCode 225. 用队列实现栈

  • 一、题目描述
  • 二、解题
    • 1、主要思路解析
    • 2、先实现栈
    • 3、实现各个接口
      • 3.1、初始化接口
      • 3.2、push接口
      • 3.3、pop接口
      • 3.4、myStackTop接口
      • 3.5、myStackEmpty接口
      • 3.6、myStackFree接口

一、题目描述

原题连接: 225. 用队列实现栈
题目描述:
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

  1. void push(int x) 将元素 x 压入栈顶。
  2. int pop() 移除并返回栈顶元素。
  3. int top() 返回栈顶元素。
  4. boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

  1. 你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
  2. 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:

输入:
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

提示:

  1. 1 <= x <= 9
  2. 最多调用100 次 push、pop、top 和 empty
  3. 每次调用 pop 和 top 都保证栈不为空

进阶: 你能否仅用一个队列来实现栈。

二、解题

1、主要思路解析

其实这一题并不能算做一道算法题,它应该是一道练习题,目的就是练习我们对于队列和栈的熟悉度。
所以这道题其实逻辑并不复杂,复杂的是结构,因为我们将要用到很多的结构,还有很多结构套结构的地方。

想要解决这道题,其实主要思路就只有一个,就是想办法使用两个队列来模拟出栈的先进后出模型。
但是要怎么模拟呢?我们可以把注意力放到题目中的一个信息,也就是关于pop接口的:“int pop() 移除并返回栈顶元素。”
我们知道,正经的栈的实现其实是并不需要返回栈顶元素的,但它这里为什么需要返回呢?
我们知道队列的规则是先进先出:
在这里插入图片描述
所以后面进去的数据都在队尾,而我们出数据只能在队头出,也就是所我们不能直接的将后面进去的在队尾的数据直接出,所以我们就不能直接使用一个队列实现栈的后进先出结构。
但我们可以使用两个队列:
在这里插入图片描述
然后当我们每次要pop数据的时候,我们先将不为空的队列中的数据出队,并将这些数据入到为空的一个队列中,直到出到只剩一个数据:
在这里插入图片描述
如上图,最后我们再将queue1中仅剩的一个数据在出队,但这次我们不把它入到queue2中,我们把它返回。
所以这就是为什么,题目要要求我们将出队的数据返回,其实题目就是通过检查我们返回的数据是否是最后面进去的来判断我们写的栈是否满足后进先出的规则。
而对于,压栈push,我们可以直接将数据入队到不为空的队列:
在这里插入图片描述
所以我们这种实现方案在每次执行完push或pop之后,都会有其中一个队列是为空的。

那这就是这道题的主要思路了,其实逻辑并不是很复杂。

2、先实现栈

因为我这里使用的是C语言,而C语言是并没有封装数据结构的,所以我们还得自己造轮子。
那我们就先将队列这个结构实现一下,我这里就直接复制我之前在【数据结构】和栈一样简单的结构——队列所写的队列了:

// 重定义数据类型
typedef int QDataType;
// 定义节点类型
typedef struct QueueNode {
	struct QueueNode* next;
	QDataType data;
} QueueNode;
// 定义队列类型
typedef struct Queue {
	QueueNode* head;
	QueueNode* tail;
} Queue;
// 队列的初始化
void QueueInit(Queue* pq);
// 队列的入队
void QueuePush(Queue* pq, QDataType x);
// 队列的出队
void QueuePop(Queue* pq);
// 返回队列的对头元素
QDataType QueueFront(Queue* pq);
// 返回队列的队尾元素
QDataType QueueBack(Queue* pq);
// 返回队列中的节点个数
int QueueSize(Queue* pq);
// 判断队列是否为空
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);
// 队列的初始化
void QueueInit(Queue* pq) {
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}
// 队列的入队
void QueuePush(Queue* pq, QDataType x) {
	assert(pq);
	// 创建一个新节点
	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NULL == newNode) {
		perror("malloc fail!\n");
		exit(-1);
	}
	newNode->data = x;
	if (NULL == pq->head) {
		pq->head = newNode;
		pq->tail = newNode;
		pq->tail->next = NULL;
	}
	else {
		pq->tail->next = newNode;
		pq->tail = pq->tail->next;
		pq->tail->next = NULL;
	}
}
// 队列的出队
void QueuePop(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	// 如果对头为空了,我们也要把队尾也给置空,避免野指针
	if (NULL == pq->head) {
		pq->tail = NULL;
	}
}
// 返回队列的对头元素
QDataType QueueFront(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}
// 返回队列的队尾元素
QDataType QueueBack(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}
// 返回队列中的节点个数
int QueueSize(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	QueueNode* cur = pq->head;
	int size = 0;
	while (cur) {
		size++;
		cur = cur->next;
	}
	return size;
}
// 判断队列是否为空
bool QueueEmpty(Queue* pq) {
	assert(pq);
	return pq->head == NULL;
}
// 销毁队列
void QueueDestroy(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	QueueNode* cur = pq->head;
	QueueNode* next = cur->next;
	while (cur) {
		next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = NULL;
	pq->tail = NULL;
}

3、实现各个接口

3.1、初始化接口

初始化接口其实只有一个,那就是myStackCreate,因为oj的后台在测试的时候都是通过调用接口来测试的,所以我们需要将创建栈的接口也写上。
但同时我们也要先把,栈的结构体先写上,其实栈的结构体里就只有两个队列变量:

typedef struct {
    Queue queue1;
    Queue queue2;
} MyStack;

MyStack* myStackCreate() {
    MyStack *stack = (MyStack*)malloc(sizeof(MyStack));
    if (NULL == stack) {
        perror("malloc fail!\n");
        exit(-1);
    }
    QueueInit(&stack->queue1);
    QueueInit(&stack->queue2);
    return stack;
}

其实这里的栈的结构体里面的两个队列也可以使用指针的,但这样会比较麻烦一些,因为如果写成指针的话就需要在额外的开辟两个队列的空间,最后也要额外的销毁这两个队列指针,所以这里就直接创建成变量了。
然后在函数myStackCreate中我们要对这两个队列进行初始化。

3.2、push接口

因为我们是要把数据入到不为空的队列中,为了代码不冗余,我们可以先假设其中一个队列不为空,用一个指针NonEmptyQueue来指向。然后再进行判断,如果预先假设的那个队列不为空,就让NonEmptyQueue指针指向另一个队列。最后我们就对NonEmptyQueue指向的这个队列执行入队操作即可,这个操作我们就直接调用我们事先实现好的队列中的pus接口即可:

void myStackPush(MyStack* obj, int x) {
    // 压栈我们要把数据入到不为空的队列的队尾
    Queue *NonEmptyQueue = &obj->queue1; // 默认queue1队列不为空
    Queue *EmptyQueue = &obj->queue2; 
    if (QueueEmpty(&obj->queue1)) { // 如果队列1为空,我们就要替换一下
        NonEmptyQueue = &obj->queue2;
    } 
    // 将数据入到不为空的队列
    QueuePush(NonEmptyQueue, x);
}

3.3、pop接口

因为我们事先要把数据从不为空的队列中出队在入队到为空的队列中,所以和push一样,我们还是先假设其中一个队列不为空,然后在经过后面的判断,确保NonEmptyQueue 和EmptyQueue 指向正确的队列。
因为我们这里要操作的是两个队列,所以我们这里要确保这两个指针都指向正确地队列,而对于上面的push,我们就只需要保证一个指针即可。

int myStackPop(MyStack* obj) {
    // 弹栈我们要先将不为空的队列先执行出队操作,并将数据入到为空的那个栈中,
    // 直到不为空的队列出到队列中只剩一个元素
    Queue *NonEmptyQueue = &obj->queue1; // 默认queue1队列不为空
    Queue *EmptyQueue = &obj->queue2; 
    if (QueueEmpty(&obj->queue1)) { // 如果队列1为空,我们就要替换一下
        NonEmptyQueue = &obj->queue2;
        EmptyQueue = &obj->queue1;
    }
    while (QueueSize(NonEmptyQueue) > 1) {
        // 先保存队头元素的值
        int temp = QueueFront(NonEmptyQueue);
        QueuePop(NonEmptyQueue);
        QueuePush(EmptyQueue, temp);
    } 
    // 先保存要返回的值
    int returnVal = QueueFront(NonEmptyQueue);
    QueuePop(NonEmptyQueue);
    return returnVal;
}

最后在我们pop最后一个数据之前,要先将要返回的数据保存,因为接下来我们就要pop这个数据。
最后返回即可。

3.4、myStackTop接口

这个接口其实没什么好说的。

int myStackTop(MyStack* obj) {
    Queue *NonEmptyQueue = &obj->queue1; // 默认queue1队列不为空
    Queue *EmptyQueue = &obj->queue2; 
    if (QueueEmpty(&obj->queue1)) { // 如果队列1为空,我们就要替换一下
        NonEmptyQueue = &obj->queue2; // 因为只需要操作一个队列,所以我们这里只需要保证一个指针
    }
    return QueueBack(NonEmptyQueue);
}

3.5、myStackEmpty接口

这个接口其实也很简单,我们直接返回两个队列是否为空的判断结果即可:

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->queue1) && QueueEmpty(&obj->queue2);
}

3.6、myStackFree接口

因为队列中的元素(节点)也是动态开辟出来的,所以我们再释放栈之前要先将其中一个不为空的队列先给释放掉,然后再释放掉栈。

void myStackFree(MyStack* obj) {
    // 先要销毁队列,因为我们始终保持着一个队列为空,所以销毁只需要销毁空的那个队列即可
    if (!myStackEmpty(obj)) { // 如果栈不为空,那我们就要先销毁队列
        Queue *NonEmptyQueue = &obj->queue1; // 默认queue1队列不为空
        Queue *EmptyQueue = &obj->queue2; 
        if (QueueEmpty(&obj->queue1)) { // 如果队列1为空,我们就要替换一下
            NonEmptyQueue = &obj->queue2;
        }
        QueueDestroy(NonEmptyQueue);
    }
    // 再销毁栈
    free(obj);
}

而对于栈指针的置空,我们这里没有选用二级指针,所以在这个接口内部是置空不了的,但这里是oj,所以我们并不需要。如果不是在oj,那我们就要讲这个操作交给使用者,就像free函数一样。

总结起来,这道题的逻辑操作其实就那一点,较为复杂的就是对结构的运用,因为这里要都是要调用队列中的各个接口来完成操作。
所以这道题其实也是值的我们多做几次的,以熟悉对队列和栈的结构和操作。

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

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

相关文章

如何选择合适的自动化测试工具? 我教你

目录 前言 选择最佳自动化测试工具的重要性 自动化测试工具的种类 选择自动化测试工具时要考虑的因素 自动化测试工具的选择标准 前言 自动化测试是高质量软件交付领域中最重要的实践之一。在今天的敏捷开发方法中&#xff0c;几乎任一软件开发过程都需要在开发阶段的某个…

测试工具-JIRA

软件测试工具—JIRA使用 1.创建 项目&#xff1a;一般为业务线名称 问题类型&#xff1a;任务、故事、缺陷等 概要&#xff1a;bug的简要描述&#xff0c;一般写出定位地方和目前现状 Sprint&#xff1a;一般为业务线迭代简称 测试阶段&#xff1a;按照各公司的测试流程对应标…

vlc搭建rtsp直播Demo

文章目录 学习链接本地视频文件作为数据源推流步骤拉流步骤 本地摄像头作为数据源拉流步骤 学习链接 ffmpeg 使用VLC亲手搭建RTSP直播点播 本地视频文件作为数据源 推流步骤 点击媒体->流 点击添加&#xff0c;选择视频文件&#xff0c;后点击串流 点击串流后&#xf…

linux异步通知实验

一、异步通知简介 中断是处理器提供的一种异步机制&#xff0c;配置好中断后就可以让处理器去处理其他的事情&#xff0c;当中断发生以后会执行中断服务函数&#xff0c;在中断服务函数中做具体的处理。 Linux 应用程序可以通过阻塞或者非阻塞两种方式来访问驱动设备&#xff0…

04:mysql--DCL

目录 1:介绍 2:语法 3:DCL的使用 4:权限 1:介绍 DCL英文全称是Data ControlLanguage(数据控制语言)&#xff0c;用来管理数据库 用户、控制数据库的访问 权限。 2:语法 DCL-管理用户 查询用户 use mysql; select * from user; 创建用户 create user "用户名&quo…

缓存服务器概述

一、概述 主要功能&#xff1a;应用解耦&#xff0c;异步消息&#xff0c;流量削锋等问题 架构设计&#xff1a;实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性架构 常用消息队列&#xff1a;ActiveMQ&#xff0c;RabbitMQ&#xff0c;ZeroMQ&#xff0c;Kafka…

谷歌chrome浏览器升级新版后字体显示不清楚解决方案

谷歌chrome浏览器升级新版后字体显示不清楚解决方案 参考图片&#xff1a; Chrome更新至版本Chrome 109.0.5414.120 字体看不清 浏览器症状与表现 Chrome更新至版本Chrome 109.0.5414.120 字体看不清&#xff1b;会很细&#xff0c;在设置中选择自定义的字体&#xff0c;仍无法…

MySQL数据库笔记-基础篇

文章目录 MySQL概述数据库相关概念小总结 MySQL数据库版本&#xff1a;启动与停止客户端连接关系型数据库数据模型小总结 SQLSQL通用语法SQL分类DDL数据库操作查询创建数据类型数据类型的使用练习 修改删除小总结 DML添加数据修改数据删除数据小总结 DQLDCL MySQL概述 数据库相…

BI技巧丨矩阵甘特图

BOSS&#xff1a;白茶&#xff0c;我们最近有很多项目&#xff0c;能不能做个甘特图&#xff1f; 白茶&#xff1a;没问题&#xff01;&#xff08;应用市场上随便找个甘特图就OK了嘛&#xff01;&#xff09; BOSS&#xff1a;对了&#xff0c;不要那种点进来会有点卡&#xf…

二、数据结构1:单链表 模板题+算法模板(单链表)

文章目录 算法模板单链表题目模板 模板题单链表原题链接题目思路题解 算法模板 单链表题目模板 // head存储链表头&#xff0c;e[]存储节点的值&#xff0c;ne[]存储节点的next指针&#xff0c;idx表示当前用到了哪个节点 int head, e[N], ne[N], idx;// 初始化 void init() …

【001】C++的第一个程序Hello World

C的第一个应用程序&#xff08;Hello World程序&#xff09; 引言一、代码二、代码解释三、注意事项四、注释语句总结 引言 &#x1f4a1; 作者简介&#xff1a;专注于C/C高性能程序设计和开发&#xff0c;理论与代码实践结合&#xff0c;让世界没有难学的技术。 &#x1f449;…

《JavaEE》网络编程套接字

文章目录 Socket我们来解释一下上面叫做有无连接我们再来理解一下上面是可靠传输和不可靠传输面向字节流与面向数据报的区别&#xff08;后期会具体分析这个&#xff09;全双工 单双工 UDPDatagramSocketDatagramPacket我们来尝试写一下UDP版本的代码 TCPServerSocketSocket我们…

什么年代了?不会还有人不会插件化吧?

一&#xff0e;到底什么是插件化开发 插件化开发是将整个app拆分成很多模块&#xff0c;这些模块包括一个宿主和多个插件&#xff0c;每个模块都是一个apk&#xff0c;最终打包的时候将宿主apk和插件apk分开或者联合打包。 为什么要将一定要使用插件化开发呢&#xff1f; 宿…

【Linux】一文带你掌握Linux权限!

Linux权限 问题一&#xff1a;Linux下的用户分类有哪些&#xff1f;问题二&#xff1a;什么叫做权限&#xff1f;问题三&#xff1a;见一见没有权限是什么现象(看看就好了)问题四&#xff1a;权限的修改问题。chmodchownchgrp 问题五&#xff1a;其他问题(三个小问题)文件的初始…

51单片机(十五)直流电机驱动(PWM)

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

【并发编程】深入理解Java并发之synchronized实现原理

文章目录 一、synchronized 实现同步示例二、synchronized 典型错误示例三、Java 对象头与 Monitor四、synchronized代码块底层原理五、synchronized方法底层原理六、Java虚拟机对synchronized的优化 一、synchronized 实现同步示例 public class MyThread extends Thread{pri…

分布式事务之Seata讲解

文章目录 1 Seata1.1 简介1.2 架构1.3 四种事务模式1.3.1 XA1.3.1.1 定义1.3.1.2 优缺点1.3.1.3 代码中实现 1.3.2 AT1.3.2.1 定义1.3.2.2 全局锁1.3.2.2.1 AT模式脏写问题1.3.2.2.2 全局锁 1.3.2.3 AT模式优缺点1.3.2.4 与XA模式区别1.3.2.5 代码中实现 1.3.3 TCC模式1.3.3.1 …

云服务器配合CookieCloud插件,实现浏览器网站Cookie同步

CookieCloud是由方糖开发的一个浏览器网站Cookie同步工具&#xff0c;Cookie是一个可以短时间代表我们登录身份的凭证&#xff0c;CookieCloud同步Cookie其实就是在同步登录状态&#xff0c;由一个浏览器插件和一个可以自行搭建的服务器端组成&#xff0c;可以定时地、在本地加…

linux pl320 mbox控制器驱动分析 - (2) 消息传递示例

linux pl320 mbox控制器驱动分析 - &#xff08;2&#xff09;消息传递示例 1 Messaging from Core0 to Core12 Back-to-back messaging from Core0 to Core13 Messaging from Core0 to Cores 1, 2, and 3 using Auto Acknowledge4 Auto Link messaging from Core0 to Core1 us…

算法设计 || 第5题:钓鱼问题-北京大学网站在线算法题(贪心算法)

目录 &#xff08;一&#xff09;题目网址视频网址 &#xff08;二&#xff09;手写草稿思考 Part1: 慕课PPT Part2: 笨蛋的学习 &#xff08;一&#xff09;题目网址视频网址 北京大学网站在线算法题&#xff1a;1042 -- Gone Fishing (poj.org) 视频讲解&#xff08;北…