栈与队列基础:应用场景与经典面试题

文章目录

    • 前言
    • 一、先把栈给你扒得明明白白,连食堂阿姨都能懂
      • [1.1 到底什么是栈?别背定义,看生活例子就懂了](#1.1 到底什么是栈?别背定义,看生活例子就懂了)
      • [1.2 栈的核心操作,就这4个,多一个都没有](#1.2 栈的核心操作,就这4个,多一个都没有)
      • [1.3 栈的两种实现方式,数组vs链表,面试常问的坑都在这](#1.3 栈的两种实现方式,数组vs链表,面试常问的坑都在这)
    • 二、队列:别和栈搞混了,这俩是双胞胎但性格完全相反
      • [2.1 什么是队列?还是看生活例子,一秒就懂](#2.1 什么是队列?还是看生活例子,一秒就懂)
      • [2.2 队列的核心操作,同样4个,和栈对应着记](#2.2 队列的核心操作,同样4个,和栈对应着记)
      • [2.3 队列的4种常见类型,面试必考的全在这](#2.3 队列的4种常见类型,面试必考的全在这)
        • [2.3.1 循环队列:解决普通队列的假溢出问题](#2.3.1 循环队列:解决普通队列的假溢出问题)
        • [2.3.2 双端队列(Deque):栈和队列的结合体,全能选手](#2.3.2 双端队列(Deque):栈和队列的结合体,全能选手)
        • [2.3.3 优先队列:VIP专属通道,谁优先级高谁先上](#2.3.3 优先队列:VIP专属通道,谁优先级高谁先上)
    • 三、别觉得这俩货没用!2026年了,从CRUD到AI大模型,到处都是它们的身影
      • [3.1 栈的核心应用场景,从操作系统底层到AI大模型,全是它](#3.1 栈的核心应用场景,从操作系统底层到AI大模型,全是它)
        • [3.1.1 函数调用栈:操作系统的底层基石,没有它程序根本跑不起来](#3.1.1 函数调用栈:操作系统的底层基石,没有它程序根本跑不起来)
        • [3.1.2 括号匹配:IDE语法校验的核心逻辑,你天天都在用](#3.1.2 括号匹配:IDE语法校验的核心逻辑,你天天都在用)
        • [3.1.3 AI领域专属应用:大模型思维链CoT的底层逻辑](#3.1.3 AI领域专属应用:大模型思维链CoT的底层逻辑)
      • [3.2 队列的核心应用场景,从秒杀系统到大模型服务,全靠它扛着](#3.2 队列的核心应用场景,从秒杀系统到大模型服务,全靠它扛着)
        • [3.2.1 任务调度与流量削峰:后端开发的万金油,大模型服务的核心](#3.2.1 任务调度与流量削峰:后端开发的万金油,大模型服务的核心)
        • [3.2.2 广度优先搜索BFS:算法的核心,自动驾驶路径规划全靠它](#3.2.2 广度优先搜索BFS:算法的核心,自动驾驶路径规划全靠它)
        • [3.2.3 大模型解码核心:beam search束搜索,底层就是优先队列](#3.2.3 大模型解码核心:beam search束搜索,底层就是优先队列)
    • 四、面试被问栈和队列直接慌?2026年最高频的5道经典面试题,手把手给你讲明白
      • [4.1 面试题1:用两个栈实现队列(剑指Offer原题,10个公司面试9个考)](#4.1 面试题1:用两个栈实现队列(剑指Offer原题,10个公司面试9个考))
      • [4.2 面试题2:用两个队列实现栈(和上面的题成对出现,面试必问)](#4.2 面试题2:用两个队列实现栈(和上面的题成对出现,面试必问))
      • [4.3 面试题3:有效的括号(LeetCode第20题,入门必刷,面试100%会考)](#4.3 面试题3:有效的括号(LeetCode第20题,入门必刷,面试100%会考))
      • [4.4 面试题4:滑动窗口最大值(LeetCode第239题,中大厂必考题,字节腾讯最爱考)](#4.4 面试题4:滑动窗口最大值(LeetCode第239题,中大厂必考题,字节腾讯最爱考))
      • [4.5 面试题5:TOP K问题(海量数据找前K大的数,互联网公司必问,AI领域高频)](#4.5 面试题5:TOP K问题(海量数据找前K大的数,互联网公司必问,AI领域高频))
    • 五、最后说几句掏心窝子的话

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

前言

兄弟们,先问个扎心的问题:你是不是刷了几百道LeetCode,背了无数遍数据结构八股文,面试的时候面试官一句"栈和队列有啥区别?分别在大模型推理里有啥应用?",你瞬间就懵了?张口就来"栈是后进先出,队列是先进先出",结果面试官再追问一句"为啥函数调用用栈不用队列?大模型的beam search解码为啥用优先队列?",你当场就哑火,恨不得找个地缝钻进去?

还有更扎心的,2026年了,很多人张口闭口就是大模型、智能体、AIGC,觉得自己走在技术前沿,结果写个括号匹配的校验逻辑,硬是写了几十行if-else嵌套,最后还一堆bug;做个大模型API的流量削峰,连个队列都用不明白,上线就被用户的请求打崩系统;甚至写个递归的神经网络前向传播代码,动不动就栈溢出,还不知道问题出在哪。

我在AI行业摸爬滚打了22年,面过的候选人没有一千也有八百,最近这两年尤其是2026年,这种情况见得太多了。太多人一心追着风口跑,觉得学个大模型调包、搞个智能体框架就能薪资翻倍,却把栈和队列这种最基础、最核心的数据结构丢到了九霄云外。但现实就是,不管是传统CRUD开发,还是现在最火的AI大模型、自动驾驶、智能体开发,底层逻辑全是这些基础数据结构撑起来的。你去看2026年国内所有大厂的校招、社招面试,不管是后端岗还是AI算法岗,栈和队列永远是绕不开的必考点,不是面试官喜欢考八股,而是这东西真的是你写代码的基本功,基本功不牢,别的全是空中楼阁。

很多人说栈和队列太简单了,不就是两个存取数据的容器吗?但我敢说,80%的程序员,都没真正把这两个货搞明白。这篇文章,我就用最通俗的段子、最接地气的类比,把栈和队列给你扒得明明白白,从底层原理到2026年最新的应用场景,再到面试最高频的经典题,全给你讲透,高中生都能看懂。

一、先把栈给你扒得明明白白,连食堂阿姨都能懂

1.1 到底什么是栈?别背定义,看生活例子就懂了

很多教材一上来就给你甩"栈是一种限定仅在表尾进行插入和删除操作的线性表,遵循后进先出LIFO原则",看完你更懵了。其实栈这东西,你天天都在接触,只是你没发现而已。

打个最通俗的比方:你去公司食堂打饭,后厨的阿姨把洗干净的盘子,一个一个叠起来放好,第一个洗好的盘子在最底下,最后一个洗好的盘子在最上面。阿姨打饭的时候,只能从最上面拿盘子,最后放上去的盘子,第一个被拿走;最先放上去的盘子,最后才会被拿走。这,就是栈!

还有很多例子:你玩玩具枪的弹夹,装子弹的时候,一颗一颗压进去,第一颗压进去的在弹夹最底下,最后一颗压进去的在最上面,开枪的时候,先打出去的是最后压进去的那颗子弹;你叠衣服,最后叠好的衣服放在最上面,早上穿衣服,先拿的是最上面的那件;甚至你吃薯片,最后装进去的薯片在最上面,你先吃的也是最上面的。

说白了,栈的核心规矩就一条:后进先出(Last In First Out,简称LIFO),最后进来的,第一个出去,就这么简单。

1.2 栈的核心操作,就这4个,多一个都没有

很多教材把栈的操作讲得花里胡哨,其实栈的核心操作,就4个,你把这4个搞懂,栈的操作就全明白了,我还是用食堂叠盘子的例子给你讲:

  1. 入栈(push):就是把洗好的盘子,放到盘子堆的最上面,对应就是把数据放到栈的顶端。
  2. 出栈(pop):就是把盘子堆最上面的那个盘子拿走,对应就是把栈顶端的数据取出来,同时把这个数据从栈里删掉。
  3. 取栈顶(peek):就是低头看看盘子堆最上面的盘子是啥样的,不拿走,对应就是看看栈顶端的数据是啥,不删除这个数据。
  4. 判空(isEmpty):就是看看盘子堆里还有没有盘子,对应就是判断栈里还有没有数据。

就这4个操作,没有别的花活,栈所有的功能,都是基于这4个操作实现的。很多人用Python写代码,直接用list就实现了栈,append()方法就是入栈push,pop()方法(不带参数)就是出栈pop,list[-1]就是取栈顶peek,len(list)==0就是判空isEmpty,几行代码就搞定,非常方便。

1.3 栈的两种实现方式,数组vs链表,面试常问的坑都在这

栈的实现,本质上就两种,一种是基于数组实现的顺序栈,一种是基于链表实现的链式栈,我给你讲明白各自的优缺点,还有面试会追问的坑。

首先是顺序栈(数组实现),这个是最常用的,就像我们用Python的list实现栈一样,底层是一块连续的内存空间,用一个指针(下标)标记栈顶的位置。

  • 优点:实现简单,存取数据的速度快,因为是连续的内存空间,CPU缓存命中率高。
  • 缺点:数组的大小是固定的(静态数组),会有栈溢出的问题;就算是动态数组,频繁扩容的时候,需要重新申请内存,还要把原来的数据复制过去,有性能开销。

然后是链式栈(链表实现),用单链表就能实现,每个节点存数据和下一个节点的指针,栈顶就放在链表的头节点位置,入栈就是在头节点前面加一个新节点,出栈就是删掉头节点。

  • 优点:没有固定的大小限制,用多少内存就申请多少,不会有栈溢出的问题,只要内存够,就能一直入栈。
  • 缺点:每个节点都要存指针,内存开销大;而且节点的内存是不连续的,CPU缓存命中率低,存取速度比顺序栈慢。

这里给兄弟们提个面试常问的坑:2026年了,很多人用Python的list当栈用,但是很少有人知道,list的底层是动态数组,当你频繁push大量数据的时候,list会自动扩容,每次扩容都会申请一块更大的内存,把原来的数据复制过去,这个过程是有性能开销的。如果你提前知道要存多少数据,最好提前给list初始化好大小,避免频繁扩容,这个细节,你面试的时候说出来,面试官直接对你刮目相看。

二、队列:别和栈搞混了,这俩是双胞胎但性格完全相反

很多人学完栈,再学队列,直接就搞混了,觉得这俩不都是线性表吗?其实它俩就像一对双胞胎,长得像,但性格完全相反,栈是"后进先出",队列是"先进先出",完全反过来了。

2.1 什么是队列?还是看生活例子,一秒就懂

同样,别背教材里的定义,还是看你天天接触的例子:你去食堂打饭,要排队,第一个来排队的人,站在队伍最前面,第一个打到饭,打完就走;后面来的人,只能站在队伍的最后面排队,前面的人打完饭,才能轮到你。先排队的先吃饭,后排队的后吃饭,绝对不会出现后面的人插到前面先打饭的情况,就算阿姨的手再抖,也不会坏了这个规矩。这,就是队列!

还有很多例子:高速收费站的排队车辆,先到的先过收费站,后到的后过;你去医院门诊排队看病,先挂号的先看医生,后挂号的后看;甚至你给客服打电话,排队等待接入,先打进来的电话,先被客服接入。

说白了,队列的核心规矩也只有一条:先进先出(First In First Out,简称FIFO),最先进来的,第一个出去,就这么简单。

2.2 队列的核心操作,同样4个,和栈对应着记

队列的核心操作,也是4个,和栈的操作对应着记,非常好记,还是用排队打饭的例子给你讲:

  1. 入队(enqueue):就是新来的人,站到队伍的最后面排队,对应就是把数据放到队列的尾部。
  2. 出队(dequeue):就是队伍最前面的人,打完饭走了,对应就是把队列头部的数据取出来,同时把这个数据从队列里删掉。
  3. 取队头(peek):就是看看队伍最前面的人是谁,不叫他走,对应就是看看队列头部的数据是啥,不删除这个数据。
  4. 判空(isEmpty):就是看看队伍里还有没有人,对应就是判断队列里还有没有数据。

就这4个操作,队列所有的功能,都是基于这4个操作实现的,是不是非常简单?

2.3 队列的4种常见类型,面试必考的全在这

普通的队列很好理解,但是面试的时候,很少只考普通队列,更多的是考循环队列、双端队列、优先队列,这些也是2026年AI开发、后端开发里用的最多的,我一个个给你讲明白。

2.3.1 循环队列:解决普通队列的假溢出问题

先给兄弟们讲个普通队列的坑:如果你用数组实现普通队列,用两个指针分别标记队头和队尾,入队的时候队尾指针往后移,出队的时候队头指针往后移,时间长了,队头指针前面会有很多空的内存空间,但是队尾指针已经到了数组的最后面,没法再入队了,这就是假溢出------数组里明明有空位置,却没法入队了。

怎么解决这个问题?循环队列就来了。通俗来讲,循环队列就是把数组的首尾连起来,变成一个环形的结构,队尾指针到了数组最后面的时候,直接绕回到数组的开头,用前面空出来的位置,完美解决了假溢出的问题,还能重复利用数组的内存空间,不会浪费。

循环队列是面试超高频的考点,尤其是手写实现,后面的面试题里我会给你详细讲。

2.3.2 双端队列(Deque):栈和队列的结合体,全能选手

双端队列,顾名思义,就是队列的两端都可以入队和出队,队头可以入队也可以出队,队尾也可以入队也可以出队。这就厉害了,相当于把栈和队列的功能合二为一了:

  • 如果你只允许队尾入队、队尾出队,它就是一个栈;
  • 如果你只允许队尾入队、队头出队,它就是一个普通队列。

双端队列在实际开发里用的非常多,比如后面要讲的滑动窗口最大值问题,就是用双端队列实现的,Python里的collections.deque就是现成的双端队列,性能非常高,入队出队的时间复杂度都是O(1)。

2.3.3 优先队列:VIP专属通道,谁优先级高谁先上

优先队列,就打破了普通队列"先进先出"的规矩,它不管你什么时候入队的,只看你的优先级,优先级最高的,永远第一个出队。

还是用生活例子讲:你去医院排队看病,普通病人按挂号顺序排队,但是突然来了一个危重的急诊病人,他的优先级最高,不管他什么时候来的,医生都会先给他看病,这就是优先队列。

优先队列在2026年的技术开发里,用的简直太多了:大模型API的请求调度,VIP用户的请求优先级更高,先处理;自动驾驶的任务调度,避障的任务优先级最高,先执行;还有大模型生成文本的时候,beam search束搜索,找前K个概率最高的token,底层就是优先队列实现的。

优先队列的底层,一般是用堆来实现的,小顶堆和大顶堆,后面的TOP K面试题里,我会给你详细讲。

三、别觉得这俩货没用!2026年了,从CRUD到AI大模型,到处都是它们的身影

很多兄弟说,我学这个有啥用?我写CRUD又用不到,搞大模型也不用自己写栈和队列。那你就大错特错了,我可以这么说,你每天用的软件、写的代码、调用的大模型API,底层全是栈和队列在撑着,只是你没发现而已。

3.1 栈的核心应用场景,从操作系统底层到AI大模型,全是它

3.1.1 函数调用栈:操作系统的底层基石,没有它程序根本跑不起来

这个是栈最核心、最底层的应用,你写的任何代码,只要有函数调用,底层就一定用了栈。

给兄弟们通俗讲一下:比如你写了一段代码,主函数里调用了A函数,A函数里调用了B函数,B函数里调用了C函数。程序执行的时候,会先把主函数的上下文压入栈里,然后调用A函数,把A函数的上下文压入栈里,再调用B函数,把B函数的上下文压入栈里,再调用C函数,把C函数的上下文压入栈里。

执行的时候,先执行C函数,C函数执行完了,把C函数的上下文从栈顶弹出来,回到B函数继续执行;B函数执行完了,把B函数的上下文从栈顶弹出来,回到A函数继续执行;A函数执行完了,把A函数的上下文从栈顶弹出来,回到主函数继续执行。

完美符合栈的"后进先出"原则,后调用的函数,先执行完,先出栈。这里面试常问一个问题:为啥函数调用必须用栈,不能用队列? 答案很简单,因为函数调用的嵌套逻辑,就是后调用的必须先执行完,队列是先进先出,用队列的话,你得先执行完最先调用的主函数,才能执行后面的A、B、C函数,那嵌套调用直接就废了,程序根本没法跑。

还有很多AI新手常踩的坑:写递归代码的时候,动不动就栈溢出,为啥?因为每一次递归调用,都会把函数的上下文压入栈里,而栈的大小是有限的,递归层数太深,栈里装不下了,就会溢出。比如你写一个深度神经网络的前向传播,用递归实现,层数太多,直接就栈溢出了,很多人遇到这个问题,还不知道为啥,其实就是栈的基础没搞明白。

3.1.2 括号匹配:IDE语法校验的核心逻辑,你天天都在用

你写代码的时候,少写了一个括号,IDE立马就给你标红报错,这个功能的底层逻辑,就是用栈实现的。

比如你写的代码里有这样一段:if (a > 0) { for (int i=0; i<10; i++) { System.out.println(i); } },这么多括号,怎么判断是不是全匹配?用栈,逻辑非常简单:

  1. 遍历字符串,遇到左括号((、{、[),就直接入栈;
  2. 遇到右括号()、}、]),先看栈是不是空的,如果是空的,说明这个右括号没有对应的左括号,直接报错;如果栈不是空的,就取出栈顶的左括号,看是不是和当前的右括号匹配,匹配就出栈,不匹配直接报错;
  3. 遍历完整个字符串之后,看栈是不是空的,如果是空的,说明所有的括号都匹配成功了;如果不是空的,说明有左括号没有对应的右括号,还是报错。

就这么简单的逻辑,几行代码就搞定了,比你写几十行if-else靠谱多了。2026年了,所有的代码编辑器、代码大模型的语法校验模块,底层还是用这个逻辑,从来没变过。

3.1.3 AI领域专属应用:大模型思维链CoT的底层逻辑

2026年了,大家用大模型的时候,都知道让模型"一步步思考",用思维链(CoT)能大幅提升模型的推理准确率,但是很多人不知道,思维链的底层逻辑,就是栈的思想。

给兄弟们通俗讲一下:大模型做复杂推理的时候,会把一个大问题拆解成多个子问题,比如要算"10+20*(30-10)",模型会先拆解成先算括号里的30-10=20,再算20*20=400,再算10+400=410。这个过程,就是把大问题压入栈里,然后拆解成子问题,把子问题压入栈里,先解决最栈顶的子问题,解决完了出栈,再解决上一层的问题,直到把最底层的大问题解决,完美的后进先出逻辑。

还有大模型的上下文窗口管理、RAG检索的递归式文档解析,底层全是栈的逻辑,你把栈搞懂了,再去看大模型的推理逻辑,就会豁然开朗。

3.2 队列的核心应用场景,从秒杀系统到大模型服务,全靠它扛着

3.2.1 任务调度与流量削峰:后端开发的万金油,大模型服务的核心

队列最常用的场景,就是异步任务调度和流量削峰,这个不管是传统后端,还是AI大模型服务,都离不开。

先讲传统后端:比如用户在你的网站上注册,你需要给用户发激活邮件、发短信通知,这些操作不需要同步执行,如果你同步等邮件发完再给用户返回结果,用户可能要等好几秒,体验非常差。这时候你就可以把发邮件、发短信的任务,放到队列里,直接给用户返回注册成功,然后有专门的消费者,从队列里取出任务,异步执行,用户体验直接拉满。

还有秒杀系统,比如618大促,一秒钟有几十万用户请求进来,你的系统根本扛不住,这时候就可以用队列做流量削峰,把所有用户的请求,先放到队列里,系统按自己能承受的速度,从队列里取出请求一个个处理,多余的请求直接排队等待,避免系统被打崩,这就是队列最经典的用法。

2026年了,所有的大模型API服务,底层都是用队列做流量调度的。大家都知道,大模型的推理非常吃GPU算力,一块GPU一秒钟只能处理几十个请求,如果同时有几万个用户调用API,GPU直接就被打满了,服务直接崩溃。这时候就可以用队列,把用户的请求先放到队列里,按GPU的处理能力,一个个取出请求处理,既保证了服务不崩溃,还能给VIP用户设置优先队列,优先处理他们的请求,简直完美。

3.2.2 广度优先搜索BFS:算法的核心,自动驾驶路径规划全靠它

队列还有一个核心的应用场景,就是广度优先搜索(BFS),这个是算法里的核心,不管是二叉树的层序遍历,还是迷宫的最短路径查找,还是自动驾驶的路径规划,都是用队列实现的。

通俗讲一下BFS的逻辑:比如你要走迷宫,从起点到终点,找最短的路径。BFS的做法就是,从起点出发,先把起点周围能走的格子,全部加入队列里,然后从队列里取出第一个格子,再把这个格子周围能走的、没走过的格子,加入队列的尾部,就这样一层层遍历,先遍历的格子先处理,完美符合队列的先进先出原则。一旦找到终点,就是最短的路径,因为BFS是一层一层找的,最先找到的终点,一定是步数最少的。

2026年了,自动驾驶的路径规划、机器人的导航、大模型的知识图谱检索,底层全是BFS算法,也就是队列的核心逻辑,你说队列重不重要?

3.2.3 大模型解码核心:beam search束搜索,底层就是优先队列

前面讲优先队列的时候,提到了beam search,这个是所有生成式大模型解码的核心算法,2026年了,不管是GPT还是文心一言,底层解码都用了这个算法,而它的底层,就是优先队列实现的。

给兄弟们通俗讲一下:大模型生成文本的时候,是一个token一个token生成的,每生成一个token,都会预测下一个token的概率。如果每次只选概率最高的那一个token,就是贪心搜索,很容易生成重复、不通顺的文本。而beam search,就是每次都保留前K个概率最高的候选序列,这个K就是束宽。

怎么高效的保留前K个概率最高的候选序列?就是用优先队列,每次生成新的候选序列,都把它加入优先队列里,优先级就是序列的总概率,每次只保留前K个概率最高的,剩下的全部丢掉,这样既能保证生成的文本通顺,又不会有太大的计算量。

很多人天天用大模型,却不知道底层是这么基础的优先队列,你把这个原理讲给面试官听,面试官直接就知道,你不是只会调包的API调用工程师,而是真的懂底层原理。

四、面试被问栈和队列直接慌?2026年最高频的5道经典面试题,手把手给你讲明白

我在AI行业摸爬滚打了22年,面过太多候选人,也看过太多大厂的面试题,栈和队列的面试题,翻来覆去就这5道,是个公司面试就会考,我今天手把手给你讲明白,思路、代码、面试追问的坑,全给你说透,背完直接拿捏面试官。

4.1 面试题1:用两个栈实现队列(剑指Offer原题,10个公司面试9个考)

题目:用两个栈实现一个队列,支持队列的基本操作:入队enqueue、出队dequeue。

解题思路

很多人看到这个题,第一反应就是懵,两个后进先出的栈,怎么实现一个先进先出的队列?其实非常简单,就像你有两个桶,一个桶用来装水,一个桶用来倒水,两个桶倒来倒去,顺序就反过来了。

我给你通俗讲明白:我们准备两个栈,一个叫in栈 ,专门负责入队;一个叫out栈,专门负责出队。

  1. 入队操作:非常简单,直接把数据push到in栈里就行,啥也不用管;
  2. 出队操作 :这里是核心,分两步:
    • 第一步:如果out栈是空的,就把in栈里的所有元素,全部一个个pop出来,再一个个push到out栈里。这时候你会发现,in栈里的元素是先进后出,倒到out栈里之后,顺序就完全反过来了,原来in栈里最先入队的元素,现在到了out栈的栈顶;
    • 第二步:直接把out栈的栈顶元素pop出来,就是队列里最先入队的元素,完美实现了先进先出。

这里有个面试必追问的坑:只有当out栈是空的时候,才能把in栈里的元素倒过去,如果out栈里还有元素,绝对不能倒,不然顺序就乱了。比如你入队了1、2、3,in栈里是[1,2,3],倒到out栈里变成[3,2,1],这时候你出队了1,out栈里还有[3,2],这时候你又入队了4,直接push到in栈里,in栈里是[4],这时候绝对不能把4倒到out栈里,不然out栈就变成[3,2,4],再出队就会出4,而不是2,顺序就乱了。

Python代码实现

python 复制代码
class MyQueue:
    def __init__(self):
        # 初始化两个栈,in栈负责入队,out栈负责出队
        self.in_stack = []
        self.out_stack = []

    def enqueue(self, x: int) -> None:
        # 入队直接往in栈里push
        self.in_stack.append(x)

    def dequeue(self) -> int:
        # 如果out栈是空的,就把in栈里的所有元素倒到out栈里
        if not self.out_stack:
            while self.in_stack:
                self.out_stack.append(self.in_stack.pop())
        # 出队就是out栈的pop
        return self.out_stack.pop()

    def peek(self) -> int:
        # 取队头元素,和出队逻辑一样,只是不pop
        if not self.out_stack:
            while self.in_stack:
                self.out_stack.append(self.in_stack.pop())
        return self.out_stack[-1]

    def isEmpty(self) -> bool:
        # 两个栈都空,队列才是空的
        return not self.in_stack and not self.out_stack

4.2 面试题2:用两个队列实现栈(和上面的题成对出现,面试必问)

题目:用两个队列实现一个栈,支持栈的基本操作:入栈push、出栈pop。

解题思路

这个题和上面的题刚好反过来,两个先进先出的队列,实现一个后进先出的栈。核心思路就是,用两个队列,一个主队列,一个辅助队列,入栈的时候直接往主队列里放,出栈的时候,把主队列里除了最后一个元素之外的所有元素,全部转移到辅助队列里,剩下的最后一个元素,就是栈顶元素,直接出队就行,然后把主队列和辅助队列交换,下次继续用。

通俗讲一下:比如你入栈了1、2、3,主队列里是[1,2,3],现在要出栈,需要把最后入栈的3拿出来,你就把1、2从主队列里出队,放到辅助队列里,主队列里就剩下3了,把3出队,就是出栈操作,然后把主队列和辅助队列交换,现在主队列是[1,2],辅助队列是空的,下次入栈继续往主队列里放就行。

还有一个优化的思路,用一个队列就能实现栈,入队的时候,把队列里前面的所有元素,全部出队再入队,把新入队的元素放到队头,这样出队的时候,就是后进先出了,代码更简单,面试的时候说出来,直接加分。

Python代码实现(两个队列版本)

python 复制代码
from collections import deque

class MyStack:
    def __init__(self):
        # 初始化两个队列
        self.main_queue = deque()
        self.help_queue = deque()

    def push(self, x: int) -> None:
        # 入栈直接往主队列里入队
        self.main_queue.append(x)

    def pop(self) -> int:
        # 把主队列里除了最后一个元素,全部转移到辅助队列
        while len(self.main_queue) > 1:
            self.help_queue.append(self.main_queue.popleft())
        # 主队列里剩下的最后一个元素,就是栈顶元素
        res = self.main_queue.popleft()
        # 交换主队列和辅助队列
        self.main_queue, self.help_queue = self.help_queue, self.main_queue
        return res

    def top(self) -> int:
        # 取栈顶元素,和出栈逻辑一样,只是要把元素放回去
        while len(self.main_queue) > 1:
            self.help_queue.append(self.main_queue.popleft())
        res = self.main_queue.popleft()
        self.help_queue.append(res)
        self.main_queue, self.help_queue = self.help_queue, self.main_queue
        return res

    def empty(self) -> bool:
        return not self.main_queue

Python代码实现(单队列优化版本)

python 复制代码
from collections import deque

class MyStack:
    def __init__(self):
        self.queue = deque()

    def push(self, x: int) -> None:
        # 先把新元素入队
        self.queue.append(x)
        # 把前面的所有元素,全部出队再入队,把新元素放到队头
        for _ in range(len(self.queue) - 1):
            self.queue.append(self.queue.popleft())

    def pop(self) -> int:
        # 队头就是栈顶,直接出队
        return self.queue.popleft()

    def top(self) -> int:
        return self.queue[0]

    def empty(self) -> bool:
        return not self.queue

4.3 面试题3:有效的括号(LeetCode第20题,入门必刷,面试100%会考)

题目:给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合;
  2. 左括号必须以正确的顺序闭合;
  3. 每个右括号都有一个对应的相同类型的左括号。

解题思路

这个题就是栈最经典的应用,前面讲应用场景的时候已经给大家讲过核心逻辑了,这里再给大家梳理一遍,还有面试常问的坑。

核心逻辑:

  1. 先建一个哈希表,把右括号和对应的左括号映射起来,比如')'对应'(', '}'对应'{', ']'对应'[',这样遇到右括号的时候,直接就能找到对应的左括号;
  2. 初始化一个栈,遍历字符串里的每个字符:
    • 如果是左括号,直接入栈;
    • 如果是右括号,先看栈是不是空的,如果是空的,说明没有对应的左括号,直接返回False;如果栈不是空的,取出栈顶元素,看是不是和当前右括号对应的左括号一致,一致就出栈,不一致直接返回False;
  3. 遍历完所有字符之后,看栈是不是空的,如果是空的,说明所有括号都匹配成功,返回True,否则返回False。

这里有几个面试常问的坑:

  • 如果字符串的长度是奇数,直接返回False,因为括号必须成对,奇数个肯定有一个匹配不上,提前判断能节省时间;
  • 遇到右括号的时候,栈是空的,直接返回False,比如字符串是")(){}",第一个字符就是右括号,肯定无效;
  • 遍历完之后,栈不是空的,说明有左括号没有匹配的右括号,比如字符串是"({)}",遍历完栈里还有元素,无效。

Python代码实现

python 复制代码
def isValid(s: str) -> bool:
    # 如果长度是奇数,直接返回False
    if len(s) % 2 != 0:
        return False
    # 建立右括号到左括号的映射
    bracket_map = {')': '(', '}': '{', ']': '['}
    # 初始化栈
    stack = []
    for char in s:
        # 如果是右括号
        if char in bracket_map:
            # 栈空的话直接返回False,否则取栈顶元素
            top_element = stack.pop() if stack else '#'
            # 不匹配直接返回False
            if bracket_map[char] != top_element:
                return False
        # 如果是左括号,入栈
        else:
            stack.append(char)
    # 最后栈空才是有效
    return not stack

4.4 面试题4:滑动窗口最大值(LeetCode第239题,中大厂必考题,字节腾讯最爱考)

题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口,从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。

解题思路

这个题,暴力解法很简单,就是每个窗口都遍历一遍找最大值,但是时间复杂度是O(n*k),如果数组很大,k也很大,直接就超时了,面试的时候写暴力解法,面试官直接就给你挂了。

最优的解法,就是用双端队列实现,时间复杂度直接降到O(n),也是面试的时候面试官想看到的解法。

核心思路:

我们用一个双端队列,队列里存的是数组的下标 ,不是数值,而且要保证队列里的下标对应的数值,是从大到小严格递减的。

  1. 遍历数组里的每个元素,下标是i:
    • 第一步:清理队尾。如果队列不为空,而且当前元素nums[i],大于等于队尾下标对应的数值,就把队尾的下标移除,因为它不可能成为任何窗口的最大值了,有比它大的、还比它晚过期的元素在,它永远没有出头之日,直到队列为空,或者队尾下标对应的数值大于当前元素,然后把当前下标i加入队尾;
    • 第二步:清理队头。判断队头的下标,是不是已经不在当前的窗口里了,也就是队头的下标 <= i - k,如果是,就把队头的下标移除,因为它已经过期了,不在窗口里了;
    • 第三步:记录结果。当i >= k - 1的时候,说明窗口已经形成了,队头的下标对应的数值,就是当前窗口的最大值,加入结果数组里。

通俗讲一下,这个双端队列,就像一个"候选人大厅",每次来一个新的候选人,先把大厅里比他弱的、没他能打的,全部赶出去,因为有他在,这些人永远当不上最大值,然后他再进去;同时,把已经过期的、不在窗口里的候选人,从门口赶出去;最后,大厅门口的那个人,就是当前窗口里最能打的,也就是最大值。

Python代码实现

python 复制代码
from collections import deque

def maxSlidingWindow(nums: list[int], k: int) -> list[int]:
    # 初始化双端队列和结果数组
    deque_window = deque()
    res = []
    for i in range(len(nums)):
        # 清理队尾:把比当前元素小的全部移除
        while deque_window and nums[i] >= nums[deque_window[-1]]:
            deque_window.pop()
        # 把当前下标加入队尾
        deque_window.append(i)
        # 清理队头:移除已经过期的下标
        while deque_window[0] <= i - k:
            deque_window.popleft()
        # 窗口形成,记录最大值
        if i >= k - 1:
            res.append(nums[deque_window[0]])
    return res

4.5 面试题5:TOP K问题(海量数据找前K大的数,互联网公司必问,AI领域高频)

题目:给你一个无序的数组,找出其中前K大的数;进阶:如果是10亿个数字,内存放不下,怎么找前K大的数?

解题思路

这个题,是面试的超高频题,不管是后端岗还是AI算法岗,必考,因为它的应用场景太广了,大模型的beam search、推荐系统的热门商品排序、海量数据的统计,都用得到。

最优的解法,就是用**小顶堆(优先队列)**实现,时间复杂度是O(nlogK),比排序的O(nlogn)快太多,尤其是n很大,K很小的时候,优势非常明显。

核心思路:

我们维护一个大小为K的小顶堆,堆顶的元素,是堆里最小的那个,也就是当前前K大的数里,最小的那个。

  1. 遍历数组里的每个元素:
    • 如果堆的大小小于K,直接把当前元素加入堆里;
    • 如果堆的大小已经等于K了,就比较当前元素和堆顶的元素:
      • 如果当前元素比堆顶元素大,说明当前元素能进前K,就把堆顶的元素移除,把当前元素加入堆里;
      • 如果当前元素比堆顶元素小,说明它进不了前K,直接跳过;
  2. 遍历完整个数组之后,堆里的K个元素,就是数组里前K大的数。

很多人会问,为啥用小顶堆,不用大顶堆?因为大顶堆的话,你要维护所有元素的大顶堆,取前K个,时间复杂度是O(nlogn),而且如果是海量数据,内存根本放不下;而小顶堆,只需要维护大小为K的堆,内存占用非常小,就算是10亿个数据,也能一个个遍历,不用一次性加载到内存里,完美解决海量数据的问题。

对于海量数据的进阶问题,还有一个分治法的思路:把10亿个数字,分成很多份,每份都能加载到内存里,每份都找出前K大的数,然后把所有份的前K大的数合并起来,再找总的前K大的数,这个也是面试的时候会追问的,你说出来,面试官直接对你刮目相看。

Python代码实现

Python里的heapq模块,实现的就是小顶堆,直接用就行。

python 复制代码
import heapq

def topK(nums: list[int], k: int) -> list[int]:
    # 初始化小顶堆
    heap = []
    for num in nums:
        # 堆的大小小于k,直接加入
        if len(heap) < k:
            heapq.heappush(heap, num)
        else:
            # 当前元素比堆顶大,替换堆顶
            if num > heap[0]:
                heapq.heappop(heap)
                heapq.heappush(heap, num)
    # 堆里的元素就是前K大的数
    return heap

五、最后说几句掏心窝子的话

2026年了,AI行业发展的太快了,大模型、智能体、AIGC,各种新名词层出不穷,很多人都在焦虑,怕自己跟不上风口,被行业淘汰,于是疯狂的学各种框架、调各种API,觉得自己掌握了最前沿的技术。

但我在AI行业摸爬滚打了22年,见过太多的技术风口,从最早的专家系统,到机器学习,再到深度学习,再到现在的大模型,技术一直在变,但是底层的逻辑从来没变过。不管是多高大上的大模型,底层的算子调度、推理逻辑、解码算法,全是栈、队列、树、图这些最基础的数据结构和算法撑起来的。

太多人一心追着风口跑,却忘了打地基,就像建房子,地基都没打牢,就想盖摩天大楼,风一吹就倒了。你只会调大模型的API,不会优化底层逻辑,遇到问题根本不知道怎么解决,面试的时候,面试官一问底层原理,你就哑火,那你永远只能做个API调用工程师,随时都能被替代。

栈和队列,是数据结构里最基础、最简单的内容,也是你入门算法、学好AI的第一步,你把这个搞透了,后面的二叉树、动态规划、深度学习的底层逻辑,都会事半功倍。

希望这篇文章,能帮你真正把栈和队列搞明白,也希望兄弟们能沉下心来,把基础打牢,不要被风口带着跑,只有基础扎实了,你才能在技术的浪潮里,站稳脚跟,不被淘汰。

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

相关推荐
荔枝学Python1 小时前
Agent设计最强书籍:它真的把Agent讲解的非常透彻!!
人工智能·程序员·大模型·大语言模型·agent·ai大模型·智能体
YJlio1 小时前
OpenClaw v2026.4.23 更新了哪些内容?图像生成、鉴权路由、媒体持久化与排障修复深度解析
人工智能·开源项目·自动化运维·版本更新·ai agent·openclaw·gpt-image-2
YJlio1 小时前
OpenClaw v2026.4.24 更新了哪些内容?Google Meet、DeepSeek V4、实时语音与浏览器自动化深度解析
人工智能·开源项目·版本更新·ai agent·deepseek·openclaw·v4 自动化运维
QD_ANJING1 小时前
建议5月的Web前端开发都去飞书上准备面试...
前端·人工智能·面试·职场和发展·前端框架·状态模式·ai编程
林小卫很行1 小时前
Obsidian 入门40:把我的写作工作流Skill免费分享给你
人工智能·经验分享·ai写作·obsidian
aneasystone本尊1 小时前
让 OpenClaw 自己动起来:Cron 与 Heartbeat
人工智能
Betelgeuse761 小时前
从爬虫脚本到 AI 智能体:一次数据挖掘实践的完整进化
人工智能·爬虫·数据挖掘
萤萤七悬1 小时前
【人工智能训练师3级】考试准备(2026)三、实操题1.1.3-3.2.5
前端·数据库·人工智能