在计算机科学中,队列(Queue)是一种常见的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。队列通常用于需要按照顺序处理的任务,比如任务调度、缓冲、广度优先搜索等场景。本文将介绍队列的概念、基本操作以及通过一个经典算法问题——"二叉树的层序遍历"来解释队列的应用。
什么是队列?
队列是一种线性数据结构,其特点是只允许在队列的一端(通常称为队尾)插入元素,在另一端(通常称为队首)删除元素。这种特性使得队列中的元素按照先进先出的顺序进行处理。在生活中像我们去食堂排队打饭一样。
队列的基本操作
队列的基本操作包括:
- 入队(Enqueue) :将元素添加到队列的末尾。
- 出队(Dequeue) :从队列的头部删除一个元素,并返回被删除的元素。
- 获取队首元素(Front) :获取队列头部的元素,但不对队列做任何修改。
- 获取队列长度(Size) :获取队列中元素的个数。
- 判空(isEmpty) :判断队列是否为空。
队列的实现
队列可以通过数组或链表来实现。在本文中,我们将使用数组来实现一个简单的队列。
class Queue {
constructor() {
this.items = [];
}
enqueue(element) {
this.items.push(element);
}
dequeue() {
if (this.isEmpty()) {
return "Underflow";
}
return this.items.shift();
}
front() {
if (this.isEmpty()) {
return "No elements in Queue";
}
return this.items[0];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
算法题
力扣239题
我们最容易想到的方法是暴力解法:
var maxSlidingWindow = function(nums, k) {
const result = [];
const n = nums.length;
// 边界情况处理
if (n <= k) {
return [Math.max(...nums)];
}
// 找出每个窗口的最大值
for (let i = 0; i <= n - k; i++) {
let max = nums[i];
for (let j = i + 1; j < i + k; j++) {
max = Math.max(max, nums[j]);
}
result.push(max);
}
return result;
};
尽管这段代码可以正确地找到滑动窗口中的最大值,但它的时间复杂度较高。在每个窗口内部使用了一个嵌套的循环来寻找最大值,导致时间复杂度为 O(kn),其中 n 是数组的长度。在 k 比较大的情况下,算法的性能会受到影响。在力扣中有n,k值很大的测试用例,所以会超出时间限制。
为了提高算法的效率,我们可以使用双端队列(deque)来优化。Deque 可以在 O(1) 的时间复杂度内完成插入和删除操作,从而降低整体算法的时间复杂度。
var maxSlidingWindow = function(nums, k) {
let result = [];
const deque = [];
for (let i = 0; i < nums.length; i++) {
// 移除 deque 中超出窗口范围的元素
while (deque.length && deque[0] < i - k + 1) {
deque.shift();
}
// 移除 deque 中小于当前元素的元素
while (deque.length && nums[deque[deque.length - 1]] <= nums[i]) {
deque.pop();
}
// 将当前元素加入 deque
deque.push(i);
// 如果当前索引大于等于 k - 1,则将 deque 的首位元素加入结果数组
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
在重构后的代码中,我们使用了一个双端队列 deque 来保存滑动窗口中可能成为最大值的元素的索引。通过调整窗口和维护 deque,我们可以在每次移动窗口时,以常数时间内获取当前窗口的最大值。
这个优化的算法时间复杂度为 O(n),其中 n 是数组的长度,因为在每个元素只会被操作一次。相比于原来的算法,这个算法在窗口滑动过程中无需遍历窗口内的所有元素,因此性能更高。