JavaScript数据结构与算法——队列

72 阅读4分钟

队列

  • 一个 先进先出 的数据结构
  • JavaScript中没有队列,但可以用 Array 实现队列的所有功能
  • 队列的常用操作:push, shift

image.png

队列的应用场景

  • 需要 先进先出 的场景
  • 比如:食堂排队打饭、JS 异步中的任务队列、计算最近请求次数

场景一:食堂排队打饭

  • 只有一个窗口,排成队列
  • 先进先出,保证有序

场景二:JS 异步中的任务队列

  • JS 是单线程,无法同时处理异步中的并发任务
  • 使用任务队列先后处理异步任务

image.png

setTimeout(() => {
    console.log(1)
},0);
console.log(2);
// 2
// 1

场景三:计算最近请求次数

image.png

LeetCode:933. 最近的请求次数

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
  • 保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例 1:

输入: ["RecentCounter", "ping", "ping", "ping", "ping"] [[], [1], [100], [3001], [3002]]

输出: [null, 1, 2, 3, 3]

解释:

RecentCounter recentCounter = new RecentCounter();

recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1

recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2

recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3

recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

思路:

  • 越早发出的请求越早不在最近3000ms内的请求里
  • 满足先进先出,考虑使用队列

解题步骤:

  • 有新请求就入队,3000ms前发出的请求出队
  • 队列的长度就是最近请求次数
var RecentCounter = function() {
    this.q = [];
};

/** 
 * @param {number} t
 * @return {number}
*/
RecentCounter.prototype.ping = function(t) {
    this.q.push(t);
    while(this.q[0] < t - 3000) {
        this.q.shift();
    }
    return this.q.length
};

/**
 * Your RecentCounter object will be instantiated and called as such:
 * var obj = new RecentCounter()
 * var param_1 = obj.ping(t)
*/

时间复杂度:O(n)

空间复杂度:O(n)

LeetCode:225. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to backpeek/pop from frontsizeis empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:

输入: ["MyStack", "push", "push", "top", "pop", "empty"] [[], [1], [2], [], [], []]

输出: [null, null, null, 2, 2, false]

解释:

MyStack myStack = new MyStack();

myStack.push(1);

myStack.push(2);

myStack.top(); // 返回 2

myStack.pop(); // 返回 2

myStack.empty(); // 返回 False。

解题步骤:

  • pop 弹出最后一个元素,用队列 shift 弹出前 n-1 个元素并放回栈数组中,并返回第 n 个元素
var MyStack = function() {
  this.s = [];
};

/** 
 * @param {number} x
 * @return {void}
 */
MyStack.prototype.push = function(x) {
  this.s.push(x);
};

/**
 * @return {number}
 */
MyStack.prototype.pop = function() {
  let pop = [];
  let temp = [];
  while(this.s.length != 1) {
    pop.push(this.s.shift());
  }
  temp = this.s;
  this.s = pop;
  pop = temp;
  return pop[0]
};

/**
 * @return {number}
 */
MyStack.prototype.top = function() {
  return this.s[this.s.length - 1]
};

/**
 * @return {boolean}
 */
MyStack.prototype.empty = function() {
  while(this.s.length != 0) {
    return false
  }
  return true
};

/**
 * Your MyStack object will be instantiated and called as such:
 * var obj = new MyStack()
 * obj.push(x)
 * var param_2 = obj.pop()
 * var param_3 = obj.top()
 * var param_4 = obj.empty()
*/

LeetCode:232. 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

示例:

输入: ["MyQueue", "push", "push", "peek", "pop", "empty"] [[], [1], [2], [], [], []]

输出: [null, null, null, 1, 1, false]

解释:

MyQueue myQueue = new MyQueue();

myQueue.push(1); // queue is: [1]

myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)

myQueue.peek(); // return 1

myQueue.pop(); // return 1, queue is [2]

myQueue.empty(); // return false

解题思路:

  • 队列 pop 弹出第一个元素,用栈 pop 逆序弹出后 n-1 个元素放回队列数组中,并返回第一个元素
var MyQueue = function() {
  this.q = [];
};

/** 
 * @param {number} x
 * @return {void}
 */
MyQueue.prototype.push = function(x) {
  this.q.push(x);
};

/**
 * @return {number}
 */
MyQueue.prototype.pop = function() {
  let pop = [];
  let temp = [];
  while(this.q.length != 1) {
    pop.push(this.q.pop());
  }
  temp = this.q;
  this.q = pop.reverse();
  pop = temp;
  return pop[0]
};

/**
 * @return {number}
 */
MyQueue.prototype.peek = function() {
  return this.q[0]
};

/**
 * @return {boolean}
 */
MyQueue.prototype.empty = function() {
  return !this.q.length
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * var obj = new MyQueue()
 * obj.push(x)
 * var param_2 = obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.empty()
*/

LeetCode:346. 滑动窗口的平均值

给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。

实现 MovingAverage 类:

  • MovingAverage(int size) 用窗口大小 size 初始化对象。
  • double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。

示例:

输入: inputs = ["MovingAverage", "next", "next", "next", "next"] inputs = [[3], [1], [10], [3], [5]]

输出: [null, 1.0, 5.5, 4.66667, 6.0]

解释:

MovingAverage movingAverage = new MovingAverage(3);

movingAverage.next(1); // 返回 1.0 = 1 / 1

movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2

movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3

movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3

解题思路:

  • 创建一个队列
  • 队列长度小于 size 直接求平均值
  • 队列长度大于等于 size 弹出第一个元素再求平均值
  • 返回平均值
/**
 * Initialize your data structure here.
 * @param {number} size
*/
var MovingAverage = function(size) {
    this.size = size;
    this.window = [];
};

/** 
 * @param {number} val
 * @return {number}
*/
MovingAverage.prototype.next = function(val) {
    if(this.window.length >= this.size){
        this.window.shift();
    }
    this.window.push(val);
    return arg(this.window);
};

let arg = (arr) => {
    let sum = 0;
    for(let i = 0; i < arr.length; i++){
        sum = sum + arr[i];
    }
    return sum/arr.length;
}

/**
 * Your MovingAverage object will be instantiated and called as such:
 * var obj = new MovingAverage(size)
 * var param_1 = obj.next(val)
*/