Skip to content

三、栈与队列(第 19-30 题) ​

19. 以下关于栈的描述中,正确的是( ) ​

标签:【互联网/国企笔试超高频】

A. 栈是先进先出(FIFO)的结构 B. 栈具有插入和删除操作的任意性 C. 栈是后进先出(LIFO)的结构 D. 栈中没有数据元素

答案:C

【结论】 栈是后进先出(LIFO)结构,选 C。

【逐项辨析】

  • A 错:先进先出(FIFO)是队列的特性,不是栈。
  • C 对:栈是后进先出(LIFO, Last In First Out)的线性结构 —— 最后入栈的元素最先出栈。
  • B 错:栈只能在栈顶插入(push)和删除(pop),不具有操作的任意性。
  • D 错:栈中可以存放任意多个数据元素,只是操作位置受限。

【知识点】 栈的核心特征

特征说明
结构线性表
操作限制只允许在栈顶插入与删除
顺序后进先出 LIFO
基本操作push(入栈)、pop(出栈)、top(取栈顶)、isEmpty
栈底最先入栈、最后出栈,位置固定

“操作受限”是理解栈的关键 —— 它并不是不能存取元素,而是存取位置被限定在栈顶这一端。

【记忆锚点】 “栈像一摞盘子,只能从顶上拿、往顶上放”——最后放上去的最先被拿走(LIFO)。

【易混对比】 栈 vs 队列(最基础的一对对比):

栈队列
顺序后进先出 LIFO先进先出 FIFO
插入端栈顶队尾
删除端栈顶队头
典型应用递归、表达式求值、括号匹配进程调度、BFS、打印队列

【自测】 元素 1、2、3 依次入栈,则第一个出栈的元素是?

答:3。最后入栈的最先出栈。与第 21、23 题连考。

【知识关联】

  • 同库连考:与第 23 题(队列 FIFO)是「栈 vs 队列」对题;与第 26 题(栈应用)、第 27~29 题(双栈队列、两队列栈、最小栈)构成栈专题。
  • 工程实现:JVM 方法调用栈、Stack 类(已过时,应用 ArrayDeque)、ThreadLocal 的栈式上下文、括号匹配、表达式求值都用栈;Deque 接口可当栈用。
  • 408 / 国企真题:408 与国网「栈与队列」定义题必考。
  • 面试追问:①「为什么 JDK 不推荐 Stack 类?」(继承 Vector,锁开销大且语义不清,应用 Deque);②「递归为什么用栈?」(返回地址与局部变量后进先出)。

【拓展延伸】

变式:输出受限双端队列(第 22 题)、输入受限双端队列都是栈/队列的推广;「一端入同端出」退化为栈,「一端入异端出」退化为队列。

工程应用:浏览器前进后退(双栈)、undo/redo、DFS、函数调用、进制转换、迷宫回溯。


20. 设栈S的初始状态为空,元素a、b、c、d、e、f、g依次入栈,出栈序列为b、d、c、f、e、a、g,则栈S的容量至少是( ) ​

标签:【国企笔试超高频】

A. 1 B. 2 C. 3 D. 4

答案:C

【知识点】 栈容量问题的一般解法与推广

栈最小容量 = 模拟过程中「栈内元素个数」的最大值

解题三步:①按入栈顺序逐个 push;②对照出栈序列决定何时 pop;③每步记录深度,取最大值。

推广一 · 与合法性关系:若模拟中需要 pop 但栈顶不匹配 → 序列非法;合法序列必能算出峰值。n 个元素合法序列数 = 卡特兰数 C_n。

推广二 · 最坏/最好:

出栈形态峰值容量
与入栈同序(边入边出)1
完全逆序(全入再全出)n
本题交错形态3

推广三 · 递归对应:递归树的最大深度 = 隐式栈峰值;改非递归时显式栈容量按同一规则估计。不能只看出栈长度或入栈个数,必须逐步模拟——这是 408 与国网反复考的「凭感觉就错」题型。

【推导过程】 按入栈顺序逐一模拟,记录栈内元素个数的最大值

步骤操作栈内容(底→顶)深度输出
1push a[a]1—
2push b[a, b]2—
3pop → b[a]1b
4push c[a, c]2—
5push d[a, c, d]3—
6pop → d[a, c]2d
7pop → c[a]1c
8push e[a, e]2—
9push f[a, e, f]3—
10pop → f[a, e]2f
11pop → e[a]1e
12pop → a[]0a
13push g[g]1—
14pop → g[]0g

出栈序列为 b, d, c, f, e, a, g,与题目一致。全程最大深度为 3。

【逐项辨析】

  • A 1 / B 2:容量不够。模拟到第 5 步(push d)时栈内已有 3 个元素,会溢出。
  • C 3:正确。 恰好容纳模拟过程中的最大深度。
  • D 4:容量足够但不是“至少”,多出的空间被浪费。

【知识点】 栈容量问题的一般解法

栈容量 = 模拟过程中栈内元素个数的最大值

解题三步:①按入栈顺序逐个 push;②对照出栈序列决定何时 pop;③每步记录栈深度,取最大值。

【记忆锚点】 “边入边记,峰值就是容量”——容量由最拥挤的那一刻决定。

【易错提醒】 不能只凭“出栈序列有多长”或“入栈了几个元素”来猜容量,必须逐步模拟。这类题在 408 与国网笔试中反复出现,是最容易因“凭感觉”而做错的题型之一。

【自测】 若入栈顺序为 1, 2, 3, 4,出栈序列为 2, 3, 4, 1,则栈的容量至少是多少?

答:2。push1, push2, pop2, push3, pop3, push4, pop4, pop1,最大深度为 2。与第 21 题连考。

【知识关联】

  • 同库连考:与第 21 题(不可能出栈序列)是「同一模拟技能」的两种问法 —— 本题问「峰值深度」,第 21 题问「序列是否合法」;本题【自测】用的是同一套「逐步入栈出栈、跟踪峰值深度」模拟,别只数入栈个数。
  • 工程实现:JVM 栈溢出 StackOverflowError 本质就是「递归深度超过了线程栈容量」;手写算符解析、非递归 DFS 都要关心「显式栈最大深度」以预估空间。
  • 408 / 国企真题:国网真题库高频「给出入出序列求栈最小容量」;408 类似模拟题。
  • 面试追问:①「若出栈序列换成 b,d,c,f,e,g,a,容量还是 3 吗?」(需重新模拟,峰值可能不同);②「n 个元素任意合法序列,容量最坏是多少?」(n,即全部入完再出)。

【拓展延伸】

完整验算与变式:本题模拟峰值 = 3(步骤 5 与 9)。若改为入栈 1..5、出栈 5,4,3,2,1 → 峰值 5;出栈 1,2,3,4,5 → 峰值 1。

通用结论:

栈最小容量 = max_t |栈内元素个数(t)|
合法出栈序列 ⇔ 任意前缀中「已入未出」数 ≥ 0 且模拟中 pop 顶匹配

变式题:入栈 1,2,3,4,5,出栈 4,5,3,2,1 —— 模拟峰值=4(push 1,2,3,4 → pop 4;push 5 → pop 5,3,2,1)。注意 4,3,5,1,2 这类写法不合法:pop 5 之后栈内是 [1,2],栈顶是 2,必须先出 2 才能出 1。

工程应用:多线程下「工作队列深度监控」、编译器表达式栈深度、正则引擎回溯栈。


21. 一个栈的入栈序列为1, 2, 3, 4,则不可能的出栈序列是( ) ​

标签:【互联网/国企笔试高频】

A. 4, 3, 2, 1 B. 1, 2, 3, 4 C. 1, 4, 2, 3 D. 3, 2, 1, 4

答案:C

【结论】 1, 4, 2, 3 不可能是合法出栈序列,选 C。

【逐项辨析】

  • A 4, 3, 2, 1:全部入栈后依次出栈,合法。
  • B 1, 2, 3, 4:每入栈一个立即出栈,合法。
  • C 1, 4, 2, 3:不合法。 先出 1(push1, pop1),再出 4 意味着 2、3、4 已依次入栈,此时栈内自底向上为 2、3(栈顶是 3),下一个出栈只能是 3,不可能是 2。
  • D 3, 2, 1, 4:push1,2,3 → pop3,2,1 → push4, pop4,合法。

【知识点 · 关键判断技巧】 出栈序列的合法性判据(必背):

对出栈序列 p₁p₂…pₙ,若存在下标 i < j < k 使 p_j < p_k < p_i,则该序列不合法。

等价说法:序列中任一元素之后,所有比它小的元素必须按降序出现。

以本题 C(1, 4, 2, 3)为例:4 之后比 4 小的有 2 和 3,而 2 < 3 呈升序,违反判据 → 不合法。

合法序列计数:入栈序列长度为 n 时,合法出栈序列共有卡特兰数 C(2n, n)/(n+1) 个。n = 4 时为 14 个。

【记忆锚点】 “大数后面,小数必须从大到小排”——大元素出栈后,剩下比它小的元素只能按降序依次出栈。

【易混对比】 注意“入栈序列固定、出栈序列可变”:n 个元素有 n! 种排列,但只有卡特兰数种合法。本题 4 个元素共 24 种排列,合法 14 种,10 种不合法。这个比例在 408 真题中常被用来设置“有多少种合法出栈序列”的计算题。

【自测】 入栈序列为 1, 2, 3, 4, 5,下列哪个不是合法出栈序列? A. 5,4,3,2,1 B. 2,1,4,3,5 C. 3,1,2,4,5 D. 1,3,5,4,2

答:C。3 出栈后栈内为 [1, 2],下一出栈只能是 2,不可能先出 1。408 真题经典考法。

【知识关联】

  • 同库连考:与第 20 题(容量)、第 22 题(双端队列序列)构成「序列合法性」三连;同一卡特兰数公式也用于「n 个结点的二叉树形态数」——该题原列第 47 题,本改版已把它替换为 TopK(见文末修订记录),公式同为 C(2n, n)/(n+1),遇到时按同一套计数方法处理。
  • 工程实现:编译器语法分析、括号匹配本质是「出栈入栈合法性」;Java Deque 模拟即可验证序列。
  • 408 / 国企真题:408 经典;国网「栈」模块必考「不可能的出栈序列」。
  • 面试追问:①「n=4 合法序列有多少?」(卡特兰数 14);②「如何 O(n) 判断合法性?」(用栈模拟)。

【拓展延伸】

完整验算 C=1,4,2,3:

期望出 1:push1, pop1 → 栈空
期望出 4:push2,3,4 → 栈[2,3,4], pop4 → 栈[2,3]
期望出 2:栈顶是 3 ≠ 2 → 非法 ✓

判据:任一元素之后,比它小的元素必须降序出现。C 中 4 之后出现 2 再 3(升序)→ 非法。

变式:

  1. 入栈 1,2,3,4,5,哪项非法?常用 C=3,1,2,4,5(3 后 1,2 升序非法)。
  2. 统计合法数:C_n = (2n)!/(n!(n+1)!);n=1,2,3,4,5 → 1,2,5,14,42。
  3. 双端队列完全自由时 n! 都合法(第 22 题自测)。

工程应用:XML/HTML 标签匹配、undo 栈操作序列校验。


22. 元素 a、b、c、d、e 依次进入一个双端队列(允许在两端进行入队操作),但只允许在同一端进行出队操作(即输出受限的双端队列),则不可能得到的出队序列是( ) ​

标签:【国企笔试超高频】

A. b, a, c, d, e B. d, c, b, a, e C. d, b, c, a, e D. e, c, b, a, d

答案:C

【结论】 不可能得到的出队序列是 d, b, c, a, e,选 C。

【推导过程】 关键前提:d 最先出队,说明 a、b、c 三个元素此时都还留在队列中(它们先于 d 入队且尚未出队)。

“输出受限”意味着只能从同一端出队,但入队可以在两端进行。因此 a、b、c 只能通过“入前端 / 入后端”改变相对次序。穷举这 3 个元素全部入队后的可能排列

[a, b, c]   [b, a, c]   [c, a, b]   [c, b, a]

所以 d 出队后,剩余元素的出队次序只能是上述四种之一。

【逐项辨析】

  • A b 先出 —— a 入后端、b 入前端,出 b、a;之后 c、d、e 各自“入前端立即出”,得 b, a, c, d, e,可行。
  • B d 先出 —— a、b、c 依次入前端得 [c, b, a](前端入序会逆序,正是【推导过程】列出的第四种排列),d 再入前端得 [d, c, b, a],e 入后端,从前端依次出 d、c、b、a、e,可行。(若 a、b、c 依次入后端得 [a, b, c],则 d 入前端后只能出 d、a、b、c,得不到本序列)
  • C 要求 d 之后依次出 b、c、a —— 而 [b, c, a] 不在上述四种排列之内,不可能。
  • D e 先出 —— a 入后端、b 入前端、c 入前端得 [c, b, a],d 入后端得 [c, b, a, d],e 入前端,依次出 e、c、b、a、d,可行。

【知识点】 双端队列(deque)的受限形态

类型允许的入队端允许的出队端说明
输入受限双端队列仅一端两端入队受限
输出受限双端队列两端仅一端出队受限
一般双端队列两端两端完全自由
极端受限(一端入、同端出)一端同一端退化为栈
极端受限(一端入、异端出)一端另一端退化为队列

栈和队列都是双端队列的特例 —— 把双端队列的两端操作各限制掉一个,就退化成栈或队列。

【记忆锚点】 “先入队的都还在里面,排列只有那几种”——做这类题先锁定“先出队元素”入队之前那些元素的可能排列。

【易错提醒】 判断合法性必须穷举剩余元素的可能排列,不能凭感觉。输出受限双端队列是国企笔试“栈与队列”的常考变体,也是 408 的高频难点。

【自测】 一般双端队列(两端都可入可出)中,元素 1, 2, 3 依次入队,能得到多少种不同的出队序列?

答:6 种全部可行(3! = 6)。双端队列比栈更灵活,因为两端都可出队。与第 21 题(n=4 时 24 种排列只有 14 种合法,C₄=14)对照记忆;n=3 才是 C₃=5。

【知识关联】

  • 同库连考:与第 21 题(栈序列)对照 —— 栈更受限,非法序列更多;与第 19、23 题(栈/队列定义)以及「双端队列退化为栈/队列」的知识点相连。
  • 工程实现:Java ArrayDeque 是双端队列;工作窃取线程池用 deque 两端进出;滑动窗口最大值(第 14 题)用单调双端队列。
  • 408 / 国企真题:408 考过双端队列;国网真题库收录「输出受限」变体。
  • 面试追问:①「输入受限双端队列和输出受限有何对偶?」(输入受限:一端入、两端出);②「本题若允许两端出,序列还唯一否?」(更自由,非法集合更小)。

【拓展延伸】

完整验算要点:

d 最先出 ⇒ a,b,c 必仍在结构内。输出受限 ⇒ 只能同一端出。a,b,c 通过两端入队可得排列有限种,穷举后发现 [b,c,a] 不可达 → C 非法。

变式:

  1. 元素 a,b,c,d 输出受限,若 d 先出,剩余合法排列有哪些?可系统穷举验证。
  2. 「一端入、同端出」= 栈 → 合法序列=卡特兰数。
  3. 「一端入、异端出」= 队列 → 唯一序列。

工程应用:消息队列的「多生产者单消费者」、撤销栈+预览队列混合结构。


23. 以下关于队列的描述中,正确的是( ) ​

标签:【国企笔试高频】

A. 队列是先进先出(FIFO)的结构 B. 队列是先进后出(LIFO)的结构 C. 队列具有插入和删除操作的任意性 D. 队列中没有数据元素

答案:A

【结论】 队列是先进先出(FIFO)结构,选 A。

【逐项辨析】

  • B错:先进后出(LIFO)是栈的特性,不是队列。
  • A 对:队列是先进先出(FIFO, First In First Out)的线性结构 —— 最先入队的元素最先出队。
  • C错:队列只能在队尾入队、队头出队,不具有操作的任意性。
  • D错:队列中可以存放任意多个数据元素,只是操作位置受限。

【知识点】 队列的核心特征

特征说明
结构线性表
操作限制队尾入队(enqueue)、队头出队(dequeue)
顺序先进先出 FIFO
基本操作enqueue、dequeue、getFront、isEmpty
存储实现顺序队列(数组)、链队列(链表)、循环队列

“一端进、另一端出”是队列的本质 —— 与栈的“同一端进、同一端出”恰好相反。

【记忆锚点】 “队列像排队买票,先来的先买”——先入队的最先出队(FIFO)。

【易混对比】 与栈对照(见第 19 题)。记忆主线:栈是“一摞盘子”,队列是“一条队伍” —— 盘子在顶部拿放,队伍从头出、从尾进。

【自测】 元素 a、b、c 依次入队,则第一个出队的元素是?

答:a。最先入队的最先出队。与第 19 题对照记忆。

【知识关联】

  • 同库连考:与第 19 题(栈 LIFO)对题;与第 24~25 题(循环队列空/满、元素个数)构成队列专题;与第 26 题(队列应用 vs 栈应用)连考;BFS(第 56/61 题)用队列、DFS 用栈。
  • 工程实现:JDK ArrayDeque/LinkedList 可当队列;BlockingQueue 是并发生产者-消费者标准件;Kafka/RocketMQ 消息队列、线程池 ready queue、打印假脱机全是 FIFO 语义。逻辑上都是队列,存储上可以是循环数组或链表。
  • 408 / 国企真题:408 与国网「队列的特性」定义题必考;干扰项固定是「LIFO=栈」「操作任意性=线性表/双端队列」「没有数据元素」。常与循环队列公式题连排。
  • 面试追问:①「优先队列还 FIFO 吗?」(否,按优先级出队,底层常是堆);②「双端队列何时变成普通队列?」(只允许队尾入、队头出时);③「循环队列为何能解决假溢出?」(下标取模回绕,头尾相接)。

【拓展延伸】

变式问法:①「队列是?」(FIFO,本题);②「栈是?」(LIFO);③「输出受限双端队列能否得到任意序列?」(国企常考,需结合栈模拟判断)。

工程映射:CPU 中断排队、网络设备缓冲、日志异步刷盘、Web 爬虫 BFS 抓取队列、限流令牌桶,都是「先到先服务」的 FIFO 工程化。

易错提醒:「队列」与「优先队列/双端队列」不是同一概念;本题问的是经典队列。D 选项「没有数据元素」明显错——空队列是状态,不是结构定义。


24. 循环队列中,队满的判断条件是( )(设队列容量为M,队头指针front,队尾指针rear)。 ​

标签:【互联网/国企笔试高频】

A. front == rear B. (rear + 1) % M == front C. rear == M - 1 D. front == 0

答案:B

【结论】 队满条件是 (rear + 1) % M == front —— 循环队列靠“牺牲一个存储单元”来区分队空与队满,选 B。

【逐项辨析】

  • A front == rear:这是队空条件。这正是循环队列的核心难点:若不牺牲单元,队空与队满时两指针状态完全相同,无法区分。
  • B (rear + 1) % M == front:正确。 队尾再前进一格就撞上队头,说明下一个位置已被占用,判定为满。
  • C rear == M − 1:只说明队尾指针走到了数组末尾,与队满无关(队尾可以取模绕回下标 0)。
  • D front == 0:队头恰好落在 0 号位置,与队满无关。

【知识点】 设容量 M、数组下标 0 ~ M−1,front 指向队头元素,rear 指向队尾元素的下一个位置:

状态判断条件元素个数
队空front == rear0
队满(rear + 1) % M == frontM − 1
一般情况—(rear − front + M) % M
容量 M = 5,已存 4 个元素:
下标:  0    1    2    3    4
      [a]  [b]  [c]  [d]  [ ]
       ↑                   ↑
     front               rear

队空?front ≠ rear → 否
队满?(rear+1)%5 = 0 == front → 是(尽管 4 号单元还空着)

【记忆锚点】 “空则相等,满则相邻”——队空时两指针相等;队满时 rear 的下一个位置就是 front。

代价:牺牲 1 个单元,容量 M 的循环队列最多只能存 M−1 个元素 —— 这就是代码里常申请 M+1 个空间的原因。

【易混对比】 三种“循环队列”实现方式

实现方式队满条件是否牺牲空间
牺牲一个单元(rear+1) % M == front是,存 M−1 个
设标志位 tagrear == front 且 tag = 1否,存 M 个
设计数器 countcount == M否,存 M 个

【自测】 容量为 6 的循环队列(牺牲一个单元),当前 front = 3、rear = 0。先删除 1 个元素,再插入 2 个元素,此时队列中有几个元素?

答:删除后 front = 4;插入 2 个后 rear = 2。元素个数 = (2 − 4 + 6) % 6 = 4 个。国网真题库同型题。

【知识关联】

  • 同库连考:与第 25 题(元素个数公式)必须连背;与第 19、23 题(栈/队列)、第 56 题(BFS 用队列)同一模块。
  • 工程实现:Java ArrayDeque 用循环数组,但不牺牲空位——用 count 字段区分空满;教材牺牲一个单元是经典教法,工程可带计数器。
  • 408 / 国企真题:408 循环队列必考空满条件;国网真题库原题。
  • 面试追问:①「不用牺牲单元怎么区分空满?」(加 size 计数);②「front 与 rear 谁指向元素谁指向空位?」(本题:front 指队头元素,rear 指队尾下一位置 —— 必须先确认约定)。

【拓展延伸】

完整验算:

容量 M=5,下标 0..4
队空:front==rear==0
入 3 个:front=0,rear=3
再入第 4 个: rear=(3+1)%5=4 成功;再入第 5 个: (4+1)%5=0==(front) → 判为队满,**该次插入被拒绝** ✓
此时可用单元 4 个,牺牲 1 个 → 最大元素数 M-1

变式:

  1. 若 rear 指向队尾元素(不是下一位置),满条件变为 (rear+2)%M==front 或改用计数。
  2. 链式队列无此问题,空满看 head 是否 null。
  3. ArrayDeque 双倍扩容,无牺牲单元。

工程应用:环形缓冲区 ring buffer、音频/网络收发包缓冲,都用取模环绕。


25. 循环队列中,队列长度(元素个数)的计算公式为( ) ​

标签:【互联网/国企笔试常考】

A. rear - front B. rear + front C. (rear + front) % M D. (rear - front + M) % M

答案:D

【结论】 元素个数 = (rear − front + M) % M,选 D。

【逐项辨析】

  • A错:rear − front 在队尾绕回(front > rear)时会出现负数。
  • D 对:先加 M 再取模,可处理 rear < front 的情况,保证结果非负。
  • C错:公式中不出现 front 与 rear 的加法。
  • B错:同 C,rear + front 无意义。

【知识点】 循环队列长度公式的推导

情况 1:rear ≥ front(未绕回)
  元素个数 = rear − front
情况 2:rear < front(已绕回)
  元素个数 = (M − front) + rear = rear − front + M

两式统一:元素个数 = (rear − front + M) % M
示例:M = 6,front = 4,rear = 2(已绕回)
个数 = (2 − 4 + 6) % 6 = 4
验证:4 → 5 → 0 → 1,共 4 个单元 → 正确

加 M 是为了把负数抬到正数区间,取模是为了处理“加多了”的情况。

【记忆锚点】 “先补一整圈,再取余”——rear 跑到 front 前面去了,就先加一圈 M 补回来,再对 M 取余。

【易混对比】 三个公式一次记全

求什么公式
元素个数(rear − front + M) % M
队空front == rear
队满(rear + 1) % M == front

【自测】 M = 10 的循环队列,front = 8,rear = 3,队列中有几个元素?

答:(3 − 8 + 10) % 10 = 5 个。与第 24 题连考。

【知识关联】

  • 同库连考:与第 24 题(队空/队满)公式成对记忆;自测中的 front=8,rear=3,M=10 → (3−8+10)%10=5 是经典数字。
  • 工程实现:Java ArrayDeque 用 tail - head + mask;Linux kfifo 环形队列同思想。
  • 408 / 国企真题:408 与国网「求循环队列元素个数」原题。
  • 面试追问:①「为什么必须先 +M 再 %M?」(rear 可能小于 front,C 语言负数取模实现相关);②「最大能存几个?」(牺牲单元时 M−1)。

【拓展延伸】

完整验算:

公式:len = (rear - front + M) % M
M=10, front=8, rear=3
(3-8+10)%10 = 5%10 = 5 ✓

若 rear=8, front=8 → 0 空
若 rear=7, front=8 → (7-8+10)%10=9 → 满(牺牲1时最大9)

变式:「带 count」实现 isEmpty: count==0; isFull: count==capacity。

工程应用:无锁队列、DMA 描述符环、浏览器事件循环任务队列。


26. 以下哪个不是队列的应用场景? ​

标签:【互联网/国企笔试高频】

A. 操作系统的进程调度 B. 打印任务排队 C. BFS(广度优先搜索) D. 函数递归调用

答案:D

【结论】 函数递归调用用的是栈而不是队列,选 D。

【逐项辨析】

  • A 进程调度:就绪队列按 FIFO 排队,是队列的典型应用。
  • B 打印任务排队:先提交的先打印,遵循 FIFO,是队列的典型应用。
  • C BFS(广度优先搜索):用队列存储“待访问的邻接顶点”,保证按层扩展,是队列的典型应用。
  • D 函数递归调用:使用函数调用栈(运行栈;注意不是操作系统里说的系统调用栈)保存返回地址与局部变量,是栈(LIFO)的典型应用,不是队列。

【知识点】 栈与队列的典型应用对照(必背):

数据结构典型应用
栈(LIFO)函数递归调用、表达式求值(中缀转后缀、后缀求值)、括号匹配、深度优先搜索 DFS、撤销操作(Undo)、浏览器后退
队列(FIFO)进程 / 作业调度、打印任务排队、广度优先搜索 BFS、消息队列、缓冲区、CPU 中断处理排队

判断技巧:凡“按到达顺序依次处理”的用队列;凡“后进先出 / 需要回溯”的用栈。

【记忆锚点】 “递归回溯用栈,排队按序用队列”。

【易混对比】 注意 DFS 与 BFS 的对照:DFS 用栈(或递归,隐式栈),BFS 用队列。这一对是图论与栈队列模块的交叉高频考点(见第 56 题)。

【自测】 下列哪一项是栈的应用? A. 打印任务排队 B. 进程调度 C. 括号匹配 D. 消息队列

答:C。A、B、D 都是队列的应用。与第 56 题(BFS/DFS)连考。

【知识关联】

  • 同库连考:与第 19/23 题(栈 vs 队列定义)配套;与第 56/61 题(BFS 用队列、DFS 用栈/递归)交叉高频;与第 60 题(拓扑排序 Kahn 法用队列)相连。
  • 工程实现:JVM 调用栈保存局部变量与返回地址——递归深度过大 StackOverflowError,正是栈被压爆;线程池任务队列、消息中间件是队列;表达式求值(算符栈+操作数栈)、括号匹配、JSON/CSS 解析用栈;游戏寻路无权格子用 BFS 队列。
  • 408 / 国企真题:「下列哪个不是队列的应用」两边都考,答案固定指认「递归/DFS/括号匹配/表达式求值」等栈应用;反向题「哪个是栈的应用」同理。
  • 面试追问:①「递归为什么是栈?」(后调用先返回,返回地址与现场 LIFO 保存);②「拓扑排序用什么容器?」(Kahn:入度 0 顶点入队);③「单调队列/单调栈是?」(滑动窗口最值、下一个更大元素,属栈/队列的强化变体)。

【拓展延伸】

变式问法:①「不是队列应用的?」(函数递归,本题);②「DFS 隐式用了什么?」(调用栈);③「浏览器前进后退?」(双栈)。

工程映射:微服务调用链的 Trace 栈式上下文;消息队列削峰填谷;操作系统进程就绪队列;迷宫题 DFS 栈 / BFS 队列对照刷题。

易错提醒:DFS 与递归是栈,BFS 是队列,这一对几乎每套卷都考;C 选项 BFS 是队列应用,不要误判为「不是」。


27. 用两个栈可以模拟一个队列的行为。当需要执行入队操作时,应该( ) ​

标签:【互联网面试高频】

A. 直接push到栈1 B. 将栈1所有元素pop到栈2,再push到栈1 C. 直接push到栈2 D. 同时push到两个栈

答案:A

【结论】 入队时直接 push 到栈 1(输入栈),选 A。

【逐项辨析】

  • A 直接 push 到栈 1:正确。 栈 1 作为“输入栈”,所有新元素直接压入,代价 O(1)。
  • B 把栈 1 全部倒到栈 2 再 push:这是“出队”时才需要做的动作,且方向反了 —— 应为“栈 1 倒入栈 2”,不是“倒出后再压回栈 1”。
  • C 直接 push 到栈 2:栈 2 是“输出栈”,用于出队时弹出;直接压入会破坏 FIFO 顺序。
  • D 同时 push 到两个栈:元素重复存储,完全错误。

【知识点】 两个栈模拟队列的标准方法

栈 1 = 输入栈(in)    栈 2 = 输出栈(out)

入队:直接 push 到栈 1                                → O(1)
出队:① 若栈 2 非空,直接 pop 栈 2
     ② 若栈 2 为空,把栈 1 全部倒入栈 2,再 pop 栈 2   → 均摊 O(1)

关键原理:栈 1 的元素倒入栈 2 后顺序被反转,两次反转即还原 FIFO。

入队 1, 2, 3:   栈1 = [1, 2, 3](栈顶 3)
出队:倒入栈2     栈2 = [3, 2, 1](栈顶 1)
     pop 栈2 → 1   ✓ 最先入队的最先出队

【记忆锚点】 “进就进 in,出时倒一次”——入队无脑压 in 栈,出队才把 in 倒进 out 栈。

【易混对比】 均摊复杂度分析:每个元素至多被倒入栈 2 一次,所以 n 次操作总代价为 O(n),均摊 O(1)。不要因为“倒栈要 O(n)”就误判为每次 O(n)。大厂面试高频,常要求手写实现并分析均摊复杂度。

【自测】 用两个栈模拟队列,连续入队 n 个元素后再连续出队 n 个元素,总时间复杂度是多少?

答:O(n)。入队 n 次 O(n),倒栈 1 次 O(n),出队 n 次 O(n),合计 O(n),均摊每次 O(1)。与第 28 题连考。

【知识关联】

  • 同库连考:与第 28 题(两队列模拟栈)是完美对偶 —— 必须一起背;与第 23、24 题(队列基础)相连。
  • 工程实现:面试手写高频;Java 可用两个 ArrayDeque 实现;思想类似「用两个栈做 undo/redo」。
  • 408 / 国企真题:408 少见;属互联网面试高频,笔试偶有变式。
  • 面试追问:①「均摊复杂度?」(每个元素至多入栈 2、出栈 2 → 均摊 O(1));②「入队 O(1)、出队 O(n) 可以吗?」(可以,出队时倒栈;或反过来)。

【拓展延伸】

完整验算:

入队 a,b,c:全部进 in → in=[a,b,c]
出队:把 in 倒入 out → out=[c,b,a],再 pop → a ✓
再出:out 继续 pop → b
若 out 空且 in 有货,再倒一次
n 次入+出,每元素倒 1 次 → 总 O(n),均摊 O(1)

变式:一个栈+一个队列模拟栈/队列;两个栈模拟队列见第 27 题(本题)。

工程应用:日志缓冲与刷盘顺序、撤销栈与回放队列。


28. 用两个队列模拟一个栈,入栈操作时应该( ) ​

标签:【互联网面试高频】

A. 将元素入队到非空的那个队列 B. 将元素入队到空的那个队列 C. 同时入队到两个队列 D. 随意选择一个队列

答案:A

【结论】 入栈时把元素入队到非空的那个队列,选 A。

【逐项辨析】

  • A 入队到非空队列:正确。 保持所有元素集中在同一个队列中,另一个队列留空备用。
  • B 入队到空队列:会导致元素分散在两个队列,破坏后续出栈逻辑。
  • C 同时入队到两个队列:元素重复,完全错误。
  • D 随意选择:元素会分散,无法保证 LIFO 顺序。

【知识点】 两个队列模拟栈的标准方法

队列 q1、q2,始终只有一个非空(存放全部元素)

入栈:把元素入队到非空的那个队列                        → O(1)
出栈:把非空队列中除最后一个元素外的所有元素依次出队,
     并入队到另一个队列,最后把剩下的那个元素出队       → O(n)

模拟过程(入栈 1, 2, 3,再连续出栈):

操作q1q2出栈结果
入栈 1[1][]—
入栈 2[1, 2][]—
入栈 3[1, 2, 3][]—
出栈[][1, 2]出 3 ✓
出栈[1][]出 2 ✓
出栈[][]出 1 ✓

【记忆锚点】 “留一个、挪一堆”——出栈时把前面的元素全部挪到另一个队列,只留下最后一个出队。

【易混对比】 两个栈模拟队列 vs 两个队列模拟栈

两个栈模拟队列两个队列模拟栈
入操作代价O(1)O(1)
出操作代价均摊 O(1)O(n)
效率较高较低

结论:两栈模拟队列的效率明显优于两队列模拟栈 —— 前者均摊 O(1),后者每次出栈都要挪动 O(n) 个元素。这一对比常被作为面试追问。

【自测】 两个队列模拟栈,连续入栈 n 个元素后再连续出栈 n 个元素,总时间复杂度是多少?

答:O(n²)。每次出栈需挪动 O(n) 个元素,共 n 次出栈 → O(n²)。与第 27 题对照记忆。

【知识关联】

  • 同库连考:与第 27 题(双栈模拟队列)对偶连背;与第 19、23 题(栈队列定义)基础。
  • 工程实现:面试手写;「留一个、挪一堆」是标准解;复杂度分析常被追问——单次出栈 O(n)、连续入 n 再连续出 n 的总代价为 Θ(n²)(搬运 n(n−1)/2 次),不存在「均摊 O(1)」。
  • 408 / 国企真题:互联网面试高频;国企偶见。
  • 面试追问:①「入栈和出栈谁贵?」(出栈要把前面 n−1 个挪走);②「能否入栈 O(n) 出栈 O(1)?」(可以:入时把另一队列元素全部搬过来再入,再搬回)。

【拓展延伸】

完整验算:

入 1,2,3 到 q1
出栈:q1 出 1,2 到 q2,再出 3 → 3 ✓
再出:q2 现有 1,2,把 1 挪回 q1,出 2 ✓
连续入 n 再连续出 n:总搬运多少次?—— 设某次出栈时队列里有 s 个元素,必须先搬走 s−1 个才露出要出栈的那个
总计 Σ(s−1)=(n−1)+(n−2)+...+1+0=n(n−1)/2=Θ(n²)(n=6 实跑 15 次搬运,与 6×5/2=15 吻合)→ 单次出栈均摊 O(n),不是均摊 O(1)

变式:k 个队列模拟栈的最小 k 研究;两栈模拟队列见第 27 题。

工程应用:理解「用受限原语表达更丰富语义」——操作系统用信号量模拟各种同步结构。


29. 设计一个支持 push、pop、top 操作,且能在常数时间 O(1) 内获取最小元素的栈(Min Stack),最常用的实现是( ) ​

标签:【互联网面试高频】

A. 单个栈 B. 两个栈:数据栈 + 辅助栈同步记录当前栈内最小值 C. 一个栈加一个数组 D. 双端队列

答案:B【结论】 最小栈最常用的实现是“数据栈 + 辅助栈”,选 B。

【逐项辨析】

  • A 单个栈:只存数据无法 O(1) 得到最小值(除非遍历,为 O(n))。
  • B 两个栈(数据栈 + 辅助栈):正确。 辅助栈与数据栈同步记录“当前栈内最小值”,getMin 直接读辅助栈栈顶。
  • C 一个栈加一个数组:数组若随栈同步维护最小值,本质仍是“辅助栈”,只是换了个名字;若不同步则无法 O(1) 取最小。
  • D 双端队列:双端队列解决的是两端操作问题,与“O(1) 取最小值”无关。

【知识点】 最小栈(Min Stack)的实现

数据栈 data:正常 push / pop
辅助栈 minSt:与 data 同步
  push(x):  data.push(x);  minSt.push(min(x, minSt.top()))
  pop():    data.pop();    minSt.pop()
  top():    return data.top()
  getMin(): return minSt.top()

模拟(依次 push 5, 3, 7, 2,再 pop 一次):

操作data 栈minSt 栈getMin
push 5[5][5]5
push 3[5, 3][5, 3]3
push 7[5, 3, 7][5, 3, 3]3
push 2[5, 3, 7, 2][5, 3, 3, 2]2
pop[5, 3, 7][5, 3, 3]3

两栈深度始终一致,四个操作全部 O(1)。

【记忆锚点】 “辅助栈跟数据栈同呼吸,每层都记住当时的最小值”。

【易混对比】 另一种优化实现:只在“新值 ≤ 当前最小值”时才压入辅助栈,可节省空间,但 pop 时要判断“弹出的值是否等于辅助栈栈顶”才决定是否弹出辅助栈。两种实现思路不同、复杂度相同,面试中常被追问区别。

【自测】 最小栈中 getMin 的时间复杂度是多少?为什么?

答:O(1)。因为辅助栈栈顶始终保存当前栈内最小值,直接读取即可,无需遍历。LeetCode 155 原题,大厂面试手写高频。

【知识关联】

  • 同库连考:与第 14 题(单调队列)、第 93 题(TopK)同属「维护最值」;与第 19、26 题(栈应用)相连。
  • 工程实现:LeetCode 155 经典;Java 用两个 Deque<Integer>;优化版「只在更小或相等时压辅助栈」节省空间。
  • 408 / 国企真题:互联网面试超高频;国企笔试少见。
  • 面试追问:①「push/pop/top/min 都要 O(1)」如何同时满足?(辅助栈同步或存差值);②「为什么不能用一个变量存 min?」(pop 后不知次小值)。

【拓展延伸】

完整验算:

序列 push 3,2,0,4 时辅助栈(存历史最小):
3 → [3]
2 → [3,2]
0 → [3,2,0]
4 → 若「总是压入当前min」→ [3,2,0,0];getMin=0
pop 4 → 辅助 pop → min 仍 0
pop 0 → 辅助 pop → min=2

变式:最大栈;「min 栈 + 计数」压缩空间;差值法用一个栈存「与当前 min 的差」。

工程应用:监控「运行至今最小水位」、交易系统「当日最低价」滚动窗口之外的全局 min。


30. 在栈的应用中,后缀表达式(逆波兰表达式)3 4 + 5 × 6 - 的计算结果是( ) ​

标签:【互联网/国企笔试常考】

A. 25 B. 29 C. 31 D. 19

答案:B

【结论】 后缀表达式 3 4 + 5 × 6 − 的计算结果为 29,选 B。

【推导过程】 从左到右扫描,遇操作数入栈,遇运算符弹出两个操作数运算后结果入栈

步骤读入操作栈内容(底→顶)
13push 3[3]
24push 4[3, 4]
3+pop 4、3,算 3+4=7,push 7[7]
45push 5[7, 5]
5×pop 5、7,算 7×5=35,push 35[35]
66push 6[35, 6]
7−pop 6、35,算 35−6=29,push 29[29]

最终结果 = 29。

【逐项辨析】

  • A 25:中途某步运算顺序颠倒或漏算。
  • B 29:正确。 按上表逐步模拟得 29。
  • C 31:算错中间结果(如把 7×5 算成 35 后误加)。
  • D 19:中途把 7×5 误算成 25(与 5×5 混淆),再算 25−6=19;属纯计算错误。注意它不是左右操作数颠倒的结果——颠倒减法得 6−35=−29,取绝对值仍是 29(正确值),推不出 19。

【知识点】 后缀表达式(逆波兰表达式)求值规则

从左到右扫描:
  遇操作数  → 入栈
  遇运算符  → 弹出两个操作数,运算后把结果压回栈
扫描结束,栈中唯一元素即为结果

关键易错点:减法与除法是“次顶元素 op 栈顶元素” —— 即先弹出的作右操作数,后弹出的作左操作数。上表中算 35−6 时,先 pop 出 6(右操作数)、后 pop 出 35(左操作数),故为 35 − 6。

中缀转后缀的规则(必背):操作数直接输出;运算符入栈前,把栈中优先级 ≥ 它的运算符先弹出输出;遇 ( 入栈,遇 ) 弹出到 ( 为止。

【记忆锚点】 “先弹的是右手,后弹的是左手”——减法和除法一定要注意左右顺序,否则 35−6 会被算成 6−35。

【易混对比】 三种表达式对照

表达式形式扫描方向运算符位置
中缀3 + 4 × 5 − 6—操作数之间
后缀(逆波兰)3 4 + 5 × 6 −从左到右操作数之后
前缀(波兰)− × + 3 4 5 6从右到左操作数之前

【自测】 中缀表达式 (3 + 4) × 5 对应的后缀表达式是什么?其值是多少?

答:后缀为 3 4 + 5 ×,值为 35。与本题同型,国网与 408 均考过。

【知识关联】

  • 同库连考:与第 27、28 题(栈队列互模)、第 19 题(栈 LIFO)、第 21 题(出栈序列)同一知识面;表达式与编译原理、离散数学前缀/中缀/后缀互化相关。
  • 工程实现:Java 脚本引擎、计算器 App、LLVM IR 后缀式求值;Deque 作操作数栈。
  • 408 / 国企真题:408「栈的应用」必含后缀表达式;国网真题库常考计算结果。
  • 面试追问:①「中缀转后缀用什么?」(算符栈 + 输出);②「前缀表达式如何求值?」(从右向左扫描,遇运算符弹两操作数)。

【拓展延伸】

完整验算:

3 4 + 5 × 6 −
读 3,4 → 遇 + → 7 入栈
读 5 → 遇 × → 7×5=35
读 6 → 遇 − → 35−6=29 ✓

变式:

  1. 中缀 (3+4)*5-6 → 后缀 3 4 + 5 * 6 -。
  2. 前缀 - * + 3 4 5 6 同值 29。
  3. 含一元负号、幂运算右结合要改扫描方向/优先级表。

工程应用:Shunting-yard 算法、RPN 计算器(HP 经典)、编译器后端指令选择。


持续学习,持续积累。