【算法】队列

看见了

队列是一种常用的数据结构,它最大的特点是先入先出,即先进入队列中的元素最先出来。

  1. 滑动窗口的平均值
请实现如下类型MovingAverage,计算滑动窗口中所有数字的平均值,该类型构造函数的参数确定滑动窗口的大小,每次调用成员函数next时都会在滑动窗口中添加一个整数,并返回滑动窗口中所有数字的平均值。
/**
 * Initialize your data structure here.
 * @param {number} size
 */
var MovingAverage = function(size) {
    this.size = size;
    this.arr = []
    this.sum = 0
};

/** 
 * @param {number} val
 * @return {number}
 */
MovingAverage.prototype.next = function(val) {
    this.arr.push(val)
    this.sum += val
    if(this.arr.length>this.size) {
        this.sum -= this.arr.shift()
    }
    return this.sum / this.arr.length
};
  1. 最近请求次数
请实现如下类型RecentCounter,它是统计过去3000ms内的请求次数的计数器。该类型的构造函数RecentCounter初始化计数器,请求数初始化为0;函数ping(int t)在时间t添加一个新请求(t表示以毫秒为单位的时间),并返回过去3000ms内(时间范围为[t-3000,t])发生的所有请求数。假设每次调用函数ping的参数t都比之前调用的参数值大。
var RecentCounter = function() {
    this.arr = []
};

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

二叉树的广度优先遍历

应聘者在面试时经常需要使用队列来解决与广度优先搜索相关的问题。图的广度优先遍历请看图的章节。

二叉树的广度优先搜索是从上到下按层遍历二叉树,从二叉树的根节点开始,先遍历二叉树的第1层,再遍历第2层,接着遍历第3层,并以此类推。

通常基于队列来实现二叉树的广度优先搜索。从二叉树的根节点开始,先把根节点放入一个队列之中,然后每次从队列中取出一个节点遍历。如果该节点有左右子节点,则分别将它们添加到队列当中。重复这个过程,直到所有节点都遍历完为止,此时队列为空。实现二叉树广度优先搜索的经典代码如下所示:

  1. 在完全二叉树中添加节点
在完全二叉树中,除最后一层之外其他层的节点都是满的(第n层有2n-1个节点)。最后一层的节点可能不满,该层所有的节点尽可能向左边靠拢

要理解完全二叉树的定义:除了最后一层节点不满,其他层节点都是满的

/**
 * @param {TreeNode} root
 */
var CBTInserter = function(root) {
    if(root == null) return [];
    this.root = root;
    this.queue = [];
    this.queue.push(root)
    while(this.queue[0].left&&this.queue[0].right) {
        const node = this.queue.shift();
        this.queue.push(node.left)
        this.queue.push(node.right)
    }   
};

/** 
 * @param {number} v
 * @return {number}
 */
CBTInserter.prototype.insert = function(v) {
    const parent = this.queue[0]
    const child = new TreeNode(v)
    if(parent.left == null) {
        parent.left = child
    } else {
        parent.right = child;
        this.queue.shift()
        this.queue.push(parent.left)
        this.queue.push(parent.right)
    }
    return parent.val;
};

/**
 * @return {TreeNode}
 */
CBTInserter.prototype.get_root = function() {
    return this.root
};

最后分析上述代码的效率。类型CBTInserter的构造函数从本质上来说是按照广度优先搜索的顺序找出二叉树中所有既有左子节点又有右子节点的节点,因此时间复杂度是O(n)。调用函数insert在完全二叉树中每添加一个节点最多只需要在队列中删除一个节点并添加两个节点。通常,队列的插入、删除操作的时间复杂度都是O(1),因此函数insert的时间复杂度是O(1)。显然,函数get_root的时间复杂度是O(1)。

  1. 二叉树中每层的最大值
输入一棵二叉树,请找出二叉树中每层的最大值

关键就在于遍历层的时候,知道什么时候层开始,什么时候层结束。1个办法就是计数。另一个办法是利用2个队列实现广度优先遍历


/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var largestValues = function(root) {
  let current = 0;
  let next = 0;
  const queue = [];
  if(root != null) {
    queue.push(root)
    current = 1
  }

  const result = [] // 结果
  let max = -Infinity;
  while(queue.length) {
    const node = queue.shift();
    current--
    max = Math.max(max, node.val)

    if(node.left != null) {
      queue.push(node.left)
      next++
    }

    if(node.right != null) {
      queue.push(node.right)
      next++
    }

    if(current === 0) {
      result.push(max)
      current = next
      max = -Infinity
      next = 0
    }
  }
  return result
};

之前的想太复杂了,没必要2**n,

  • 双队列实现广度优先搜索
  1. 二叉树最低层最左边的值
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

/**
 * @param {TreeNode} root
 * @return {number}
 */
var findBottomLeftValue = function(root) {
    let queue = []
    let next_queue = []
    queue.push(root)
    let temp = []
    while(queue.length) {
        const node = queue.shift()
        temp.push(node)
        if(node.left) {
            next_queue.push(node.left)
        }
        if(node.right) {
            next_queue.push(node.right)
        }

        if(queue.length == 0 && next_queue.length) {
            queue = next_queue
            temp = []
            next_queue = []
        }
    }
    return temp[0].val
};
  1. 二叉树的右侧视图
如何在一棵二叉树中找出它最低层最左边节点的值?假设二叉树中最少有一个节点。例如,在如图7.5所示的二叉树中最低层最左边一个节点的值是5。
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var rightSideView = function(root) {
    if(root == null) return []
    let queue = []
    let next_queue = []
    queue.push(root)
    let temp = []
    const result = []
    while(queue.length) {
        const node = queue.shift()
        temp.push(node)
        if(node.left) {
            next_queue.push(node.left)
        }
        if(node.right) {
            next_queue.push(node.right)
        }

        if(queue.length == 0 && next_queue.length) {
            queue = next_queue
            result.push(temp.pop().val)
            temp = []
            next_queue = []
        }
    }
    if(temp.length) {
        result.push(temp.pop().val)
    } 
    return result;
};
阅读 372

solfKwolf的前端杂记
涉及个人学习心得与经验
1 篇内容引用

i'm look for a good job :)

621 声望
0 粉丝
0 条评论
推荐阅读
【ol】事件类型
Openlayers根据W3C DOM Level 2 Event接口简化实现了自己的事件类,它只提供了type和target属性以及preventDefault和stopPropagation方法。

看见了阅读 101

嘿,vue中keep-alive有个「大坑」你可能还不知道
背景是这样的,我们使用vue2开发一个在线客服使用的IM应用,基本布局是左边是访客列表,右边是访客对话,为了让对话加载更友好,我们将对话的路由使用&lt;keep-alive&gt;缓存起来。但是如果将所有对话都缓存,未...

wuwhs3阅读 418

封面图
浏览器渲染原理(一文搞懂)
浏览器渲染原理前言浏览器的主要功能总结起来就是一句话:将用户输入的 URL 转变成可视化的图像。从 URL 到 DOM 树;从 DOM 树到可视化图像;这两个过程之间的关系并没有那么明确,我们可以统称这两个过程为页面...

风不识途1阅读 786

【验证码逆向专栏】安某客滑块逆向
声明本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!本文章未经许...

K哥爬虫1阅读 246

封面图
如何用JavaScripte和HTML 实现一整套的考试答题卡和成绩表
相信在学校的你都有这样的体验,临近考试,要疯狂的“背诵”否则成绩单就要挂零,因为答题卡全部涂抹都是错的。那么毕业多年的你,没有了考试,有没有一丝怀念涂答题卡的时候,有没有好奇这个答题卡到底如何制作,...

葡萄城技术团队1阅读 355

ES10 中 Object.fromEntries() 怎么用?
微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。本文 GitHub [链接] 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

前端小智2

i'm look for a good job :)

621 声望
10 粉丝
宣传栏