开启掘金成长之旅!这是我参与「掘金日新计划 12 月更文挑战」的第 3 天,点击查看活动详情
最近在学习 Javascript 数据结构与算法相关知识,数据结构与算法对程序员来说就像是内功心法,只有不断修炼自己的内功,才能从容不迫的应对市场上各种让人应接不暇的框架,达到以不变应万变。学习数据结构与算法的过程中不仅要能看懂更要多写多练,今天就来手写下队列数据结构。
队列是遵循先进先出原则的一组有序的项,队列在尾部添加新元素,并从顶部移除元素。新添加的元素必须排在队列的队尾
手写队列
class Queue<T> {
// 队头计数 用于追踪第一个元素
private lowestCount: number;
// 队尾计数 用于控制队列的大小
private count: number;
private items: Record<number, T>;
constructor() {
this.lowestCount = 0;
this.count = 0;
this.items = {};
}
// 入队
enqueue(element: T) {
// 添加到队尾
this.items[this.count] = element;
this.count++;
}
// 出队
dequeue() {
if (this.isEmpty()) {
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
// 查看对头元素
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.lowestCount];
}
isEmpty() {
// 队尾计数和队头计数相等时
return this.count === this.lowestCount;
}
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}
size() {
// 队尾计数减队头计数
return this.count - this.lowestCount;
}
toString() {
if (this.isEmpty()) {
return "";
}
let objString = `${this.items[this.lowestCount]}`;
for (let i = this.lowestCount + 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
}
}
Queue 类和前一篇写的栈 Stack 类非常类似。主要的区别在于 dequeue 方法和 peek 方法,这是由于先进先出和后进先出原则的不同所造成的。
双端队列
双端队列(double-ended queue)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。 它同时遵守先进先出和后进先出原则,可以说它是把队列和栈相结合的一种数据结构。
class Deque<T> {
// 队头计数 用于追踪第一个元素
private lowestCount: number;
// 队尾计数 用于控制队列的大小
private count: number;
private items: Record<number, T>;
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
// 添加到队头
addFront(element: T) {
if (this.isEmpty()) {
this.addBack(element);
} else if (this.lowestCount > 0) {
this.lowestCount--;
this.items[this.lowestCount] = element;
} else {
// lowestCount === 0
for (let i = this.count; i > 0; i--) {
// 所有元素向后移一位,空出队头位置
this.items[i] = this.items[i - 1];
}
this.count++;
this.items[0] = element;
}
}
// 添加到队尾
addBack(element: T) {
this.items[this.count] = element;
this.count++;
}
// 从队头移除
removeFront() {
if (this.isEmpty()) {
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
// 从队尾移除
removeBack() {
if (this.isEmpty()) {
return undefined;
}
this.count--;
const result = this.items[this.count];
delete this.items[this.count];
return result;
}
// 获取队头元素
peekFront() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.lowestCount];
}
// 获取队尾元素
peekBack() {
if (this.isEmpty()) {
return undefined;
}
// 队尾下标为this.count - 1
return this.items[this.count - 1];
}
isEmpty() {
return this.size() === 0;
}
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}
size() {
return this.count - this.lowestCount;
}
toString() {
if (this.isEmpty()) {
return "";
}
let objString = `${this.items[this.lowestCount]}`;
for (let i = this.lowestCount + 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
}
}
使用场景
需要先进先出的场景 js 中任务队列
leetcode 练习题:933
写一个 RecentCounter 类来计算特定时间范围内最近的请求。 请你实现 RecentCounter 类: RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
var RecentCounter = function() {
this.queue = [];
};
/**
* @param {number} t
* @return {number}
*/
RecentCounter.prototype.ping = function(t) {
this.queue.push(t);
while (this.queue[0] < t - 3000) {
this.queue.shift();
}
return this.queue.length;
};
解题思路
我们可以用一个队列维护发生请求的时间,当在时间t收到请求时,将时间t入队。
由于每次收到的请求的时间都比之前的大,因此从队首到队尾的时间值是单调递增的。 当在时间 t 收到请求时,为了求出 [t-3000,t] 内发生的请求数,我们可以不断从队首弹出早于 t−3000 的时间。循环结束后队列的长度就是 [t-3000,t]内发生的请求数。