上一次讲了 栈 这一数据结构和它适合解决的问题,今天我们讲讲它的兄弟:队列
🚀 队列(Queue):先进先出的魔法窗口
在数据结构的世界中,队列(Queue) 是另一种基础但强大的工具。它遵循 「先进先出」(FIFO, First In First Out) 的原则,就像现实中的排队场景:先到的人先被服务,后到的人依次排在队尾。队列在操作系统任务调度、缓冲区管理、广度优先搜索(BFS)等场景中广泛应用。
🧱 队列(Queue)的基本概念
队列是一种运算受限的线性表,仅允许在表的一端(队尾)插入元素,在另一端(队头)删除元素。这种限制使得队列天然适合处理 顺序敏感 的问题。
🛠 操作
- push:将元素添加到队尾。
- shift:从队头移除并返回元素。
- peek:查看队头元素,但不移除。
- isEmpty:检查队列是否为空。
队列的核心特性:先进先出(FIFO),就像银行排队叫号系统。
🖼 图解
好了,你已经知道栈这一数据结构的用法和规则,现在和我一起做点题目试试吧
🧩 LeetCode 239:滑动窗口最大值
📝 问题描述
给定一个整数数组 nums 和一个滑动窗口的大小 k,请找出每个窗口中最大的值。例如:
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
❗ 暴力法思路
对于每个窗口都遍历窗口内的所有元素,找出最大值。
时间复杂度为 O(nk),在 k 较大时性能较差。
function maxSlidingWindow(nums, k) {
const n = nums.length;
const result = [];
for (let i = 0; i <= n - k; i++) {
let max = -Infinity;
for (let j = i; j < i + k; j++) {
max = Math.max(max, nums[j]);
}
result.push(max);
}
return result;
}
🚀 优化思路:单调队列
单调队列 是一种特殊的数据结构,通过维护队列中元素的单调性(递减或递增),快速找到滑动窗口的最大值或最小值。
🧠 核心思想
- 使用双端队列(Deque)存储 可能成为最大值的元素索引。
- 队列中的元素按 递减顺序 排列(从队头到队尾)。
- 遍历数组时,维护队列的单调性:
- 如果当前元素大于队尾元素,则移除队尾元素,直到队列为空或找到更大的元素。
- 将当前元素索引入队。
- 确保队头元素始终是当前窗口的最大值。
双端队列是指可以双端都能进出,不局限于尾进头出
💡 JavaScript 实现
var maxSlidingWindow = function (nums, k) {
const len = nums.length
const result = []
//维护一个单调递减队列做到实时监控最大值
const deque = [] // 双端队列,宽度等于窗口大小
if (len === 0 || k === 0) return []
if (k === 1) return nums
for (let i = 0; i < len; i++) {
// 队头的元素被排除在窗口之外时,移除原本队头元素
// 即当队头的索引小于当前索引减去窗口大小 k 时,移除队头元素
if (deque.length && deque[0] < i - k + 1) {
deque.shift()
}
// 不断地移除小于当前元素的所有元素
while (deque.length && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop()
}
// 添加当前元素的索引
deque.push(i)
// 当窗口大小达到 k 时,添加最大值到结果中
if (i >= k - 1) {
result.push(nums[deque[0]])
}
}
return result
}
// 示例测试
console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3));
// 输出: [3,3,5,5,6,7]
🧾 时间复杂度分析
- 时间复杂度:O(n)
每个元素最多入队和出队一次。 - 空间复杂度:O(n)
双端队列最多存储 n 个元素。
🧠 为什么选择单调队列?
🔍 单调队列的适用场景
- 滑动窗口问题:如寻找窗口中的最大值、最小值。
- 动态维护极值:当需要频繁查询当前数据流中的极值时。
- 避免重复比较:通过维护队列的单调性,减少冗余操作。
📌 单调队列的特性
- 单调性:队列中的元素按递增或递减顺序排列。
- 高效性:每次操作的时间复杂度接近 O(1)。
- 灵活性:可根据问题需求调整维护的单调性方向。
🎯 队列的适用范围
队列在以下场景中表现尤为出色:
- 滑动窗口问题:如本例中的滑动窗口最大值。
- 广度优先搜索(BFS):用于图或树的层序遍历。
- 任务调度:如操作系统的进程调度、打印机任务队列。
- 缓冲区管理:如网络数据包的缓存和传输。
- 模拟现实中的排队场景:如银行叫号系统、餐厅点餐队列。
📚 课后练习
- LeetCode 649. 神奇字符串
- 通过队列模拟字符生成规则。
- LeetCode 933. 最近的请求次数
- 使用队列记录请求时间,计算最近 3000 毫秒内的请求数。
- LeetCode 1670. 设计前缀和搜索
- 结合队列和前缀和技巧解决搜索问题。
🌟 总结
队列作为一种基础数据结构,其 先进先出 的特性使其在处理顺序敏感问题时表现出色。
掌握队列的应用场景和优化技巧,不仅能提升算法题的解题效率,还能为实际开发中的任务调度、缓冲区管理等问题提供解决方案。