#LC1670. 设计前中后队列
设计前中后队列
你好,同学!欢迎来到 NOI 数据结构专题强化训练。
今天我们要挑战的是一个极具实战意义的题目:设计前中后队列。这道题在 LeetCode 上考查的是对双端队列的运用,但在 NOI 竞赛中,它代表了 “动态中位数维护” 和 “双结构平衡” 这一类重要算法模型。
一、 预备知识点
在动手前,请确保你已经掌握:
- 双端队列(Deque):支持在首尾两端以 时间复杂度进行插入和删除。
- C++ STL
std::deque:常用操作包括push_front,push_back,pop_front,pop_back以及size()。 - 双队列平衡思想:使用两个队列(左队列 和右队列 )来维护一个线性结构,通过控制两个队列的长度差,使得中点操作变得高效。
二、 NOI 竞赛题目描述
题目名称:设计前中后队列 (Design Front Middle Back Queue) 时间限制:1.0s 内存限制:256MB
【问题描述】 请你设计一个队列,支持在前、中、后三个位置进行插入和弹出操作。 具体需要实现以下 6 种操作:
pushFront(val):将val添加到队列最前面。pushMiddle(val):将val添加到队列的正中间。pushBack(val):将val添加到队列最后面。popFront():从队列最前面弹出元素并返回。popMiddle():从队列正中间弹出元素并返回。popBack():从队列最后面弹出元素并返回。
【关于中点的定义】:
- 插入中点:如果队列长度为 ,新元素插入后的索引应为 (下标从 0 开始)。
- 弹出中点:如果队列长度为 ,应弹出索引为 的元素。
- 注意:如果队列为空,弹出操作均返回 。
【输入格式】 第一行包含一个整数 ,表示操作总数。 接下来的 行,每行为一个操作命令及参数(如有)。
【输出格式】
对于每个 pop 类操作,输出其返回的整数值,每个结果占一行。
【样例输入】
12
pushFront 1
pushBack 2
pushMiddle 3
pushMiddle 4
popFront
popMiddle
popMiddle
popBack
popFront
(注:此处样例简化,实际输入对应 12 次操作)
【样例输出】
1
3
4
2
-1
【数据规模与约定】
- 所有操作均在合法整数范围内。
三、 启发式思路引导:草稿纸上的推演
在 NOI 中,如果要实现 或 均摊时间复杂度的中点操作,最好的办法是**“对半开”**。
1. 核心模型:天平平衡法
想象两个容器:Left (L) 和 Right (R)。
- 我们规定一个平衡条件:右队列 的长度始终等于左队列 的长度,或者比 多 1 个。
- 即:。
2. 草稿模拟:插入 1, 2, 3
- 初始:
- pushBack 1:加入 。平衡。
- pushBack 2:加入 。不平衡( 太长),将 的首位移动到 末尾 。平衡。
- pushBack 3:加入 。平衡。
3. 中点操作推演
- 插入中点 (pushMiddle):
- 如果 ,说明总数是奇数。为了插入到 ,我们需要把 的第一个元素挪到 后,再把新元素放进 的最前面。
- 弹出中点 (popMiddle):
- 根据平衡条件,中点元素要么是 的最后一个,要么是 的第一个。具体取决于总数的奇偶性。
四、 算法流程图(伪代码逻辑)
为了在考场上写出逻辑清晰的代码,请参考以下“双队列平衡维护”流程。
graph TD
subgraph Balance_Logic
B1{L.size 大于 R.size} -- 是 --> B2(L尾移动到R头)
B1 -- 否 --> B3{R.size 大于 L.size 加 1}
B3 -- 是 --> B4(R头移动到L尾)
B4 --> End(平衡结束)
B2 --> End
B3 -- 否 --> End
end
subgraph Push_Middle
P1(开始 pushMiddle x) --> P2{L.size 等于 R.size}
P2 -- 是 --> P3(直接 R.push_front x)
P2 -- 否 --> P4(L.push_back x)
P3 --> Balance_Logic
P4 --> Balance_Logic
end
subgraph Pop_Middle
M1(开始 popMiddle) --> M2{总数为 0?}
M2 -- 是 --> M3(返回 -1)
M2 -- 否 --> M4{L.size 等于 R.size?}
M4 -- 是 --> M5(弹出 L 尾部元素并返回)
M4 -- 否 --> M6(弹出 R 头部元素并返回)
M5 --> Balance_Logic
M6 --> Balance_Logic
end
五、 复杂度分析与优化建议
- 时间复杂度:
- 所有操作均为 。
- 原因:
std::deque的首尾操作是常数时间的,而我们每次操作后最多只进行一次跨队列的元素移动(平衡维护)。
- 空间复杂度:。存储所有输入的元素。
- NOI 考场建议:
- 如果 规模较小(如 1000),使用
std::vector并在 插入也是可以接受的。但如果 达到 以上,必须使用双deque或手动维护链表。
- 如果 规模较小(如 1000),使用
六、 读题关键词总结
- “中点” (Middle):只要涉及到线性结构的中点动态变化,优先思考**“对半分割”**(双栈、双队列或双堆)。
- “索引 ”:这是平衡条件的数学依据,决定了多出的那个元素该放在左边还是右边。
- “弹出并返回”:注意空队列时的特殊处理(返回 )。
教练寄语: 这道题是“数据结构解构”的经典案例。我们把一个复杂的“前中后”队列拆解成两个简单的“双端”队列,通过一个简单的“平衡规则”将复杂度从 降到了 。这就是算法竞赛中**“分治与平衡”**的魅力。去写出你的代码实现吧!