JavaScript刷LeetCode拿offer-二叉树层序遍历篇

前言

博主最近在刷leetcode,做到二叉树套题的时候发现很多题的解题思路都是基于二叉树的层序遍历来完成的,因此写下这篇文章,记录一下二叉树层序遍历这件"神器"在实战的运用。

[leetcode] 102.二叉树的层序遍历

image.png

二叉树的层序遍历与传统的前序、中序、后序遍历都有一些区别,他是按层级、从左到右、从上到下进行遍历的,因此当我在遍历当前层节点的时候,肯定需要记录当前层所有节点的leftright,保存到队列中,进行下一轮遍历,直到节点没有leftright,则代表已经遍历到了最后一层了。

因此需要借助一个辅助数据结构——队列,队列先进后出,符合层序遍历的顺序性,其实此题就是队列 + 广度优先遍历 的一道结合题。

直接看代码吧:

/** * Definition for a binary tree node. * function TreeNode(val, left, right) { *     this.val = (val===undefined ? 0 : val) *     this.left = (left===undefined ? null : left) *     this.right = (right===undefined ? null : right) * } */
/** * @param {TreeNode} root
 * @return {number[][]} */
var levelOrder = function(root) {
    const res = [], queue = [];
    queue.push(root);
    if(root === null) return res;

    while(queue.length !== 0) {
        let level = [];
        const length = queue.length
        for(var i = 0; i < length; i++) {
            var node = queue.shift();
            level.push(node.val);
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        res.push(level);
    }
    return res;
};

接下来我们逐行分析代码。

  • 首先定义了一个结果和一个队列,对应resqueue,将顶层的root节点加入到队列中,开启循环。
  • 在每一轮while 循环中,我们从左到右依次取出节点(shift api)并且判断每个节点下一层是否有后代(left、right)的判断,如果有,则加入到队列中。
  • const length = queue.length记录了队列在每一层遍历开始时的最初状态,保证了后面的for循环遍历的内容是当前层的节点,不会因为left、right加入到队列中的节点影响到当前层的循环轮数。
  • 最终,队列中所有节点都遍历完毕,在for循环中也没有发现新的下层节点,循环结束,返回结果。

此时我们就掌握了二叉树的层序遍历了,那么如下九道力扣上的题目,只需要修改模板的两三行代码(不能再多了),便可打倒!你真的会发现,理解了层序遍历后,解决这些关联题,会如鱼得水一般简单

  • 102.二叉树的层序遍历
  • 107.二叉树的层次遍历II
  • 199.二叉树的右视图
  • 637.二叉树的层平均值
  • 429.N叉树的前序遍历
  • 515.在每个树行中找最大值
  • 116.填充每个节点的下一个右侧节点指针
  • 117.填充每个节点的下一个右侧节点指针II
  • 104.二叉树的最大深度
  • 111.二叉树的最小深度

[leetcode] 107.二叉树的层序遍历II

image.png

此题与102.二叉树的层序遍历极其相似,只需要把最后的res结果的排列顺序改变一下即可,代码架构和102完全一样。

代码:

/** * Definition for a binary tree node. * function TreeNode(val, left, right) { *     this.val = (val===undefined ? 0 : val) *     this.left = (left===undefined ? null : left) *     this.right = (right===undefined ? null : right) * } */
/** * @param {TreeNode} root
 * @return {number[][]} */
var levelOrderBottom = function(root) {
    var res = [], queue = [];
    queue.push(root);
    if(root === null) return res;
    while(queue.length) {
        let length = queue.length;
        const level = [];
        for(var i = 0; i < length; i++) {
            var node = queue.shift();
            level.push(node.val)
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        res.unshift(level);
    }
    return res;
};

参考视频:传送门

[leetcode] 199.二叉树的右视图

image.png

此题从题目描述中可以看到,需要收集每一层的最后一个节点,有了"神器"的你,此时已经有思路了吧?这不是只需要在每一轮while循环中的for里加一个判断条件,取出最后一个节点就好了?上代码!

代码:

/** * Definition for a binary tree node. * function TreeNode(val, left, right) { *     this.val = (val===undefined ? 0 : val) *     this.left = (left===undefined ? null : left) *     this.right = (right===undefined ? null : right) * } */
/** * @param {TreeNode} root
 * @return {number[]} */
var rightSideView = function(root) {
    var res = [], queue = [];
    queue.push(root);
    if(root === null ) return res;
    while(queue.length !== 0 ){
        const length = queue.length;
        for(var i = 0; i < length; i++) {
            var node = queue.shift();
            if(i === length - 1) {
                res.push(node.val);
            }
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
    }
    return res;
};

[leetcode] 637.二叉树的层平均值

image.png

此题只需要在每层节点取完以后,对节点的平均值进行一个计算即可,相比于前面几题,区别在收集的返回结果不一样,解题代码架构没有区别。

代码:

/** * Definition for a binary tree node. * function TreeNode(val, left, right) { *     this.val = (val===undefined ? 0 : val) *     this.left = (left===undefined ? null : left) *     this.right = (right===undefined ? null : right) * } */
/** * @param {TreeNode} root
 * @return {number[]} */
var averageOfLevels = function(root) {
    var res = [], queue = [];
    queue.push(root);
    if(root === null) return res;

    while(queue.length !== 0) {
        const length = queue.length;
        let total = 0;
        for(var i = 0; i < length; i++) {
            let node = queue.shift();
            total += node.val;
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        res.push(total / length);
    }
    return res;
};

[leetcode] 429.N叉树的层序遍历

image.png

此题会有一点小弯需要绕一下,首先数据结构不同,TreeNode节点是这样的:

/** * // Definition for a Node. * function Node(val,children) { *    this.val = val; *    this.children = children; * }; */

也就是说我们在每一层节点的循环中,不是再去收集节点的leftright了,而是去遍历节点的children,将children中的节点加入到队列中。

代码:

/** * // Definition for a Node. * function Node(val,children) { *    this.val = val; *    this.children = children; * }; */

/** * @param {Node|null} root
 * @return {number[][]} */
var levelOrder = function(root) {
    var res = [], queue = [];
    queue.push(root);
    if(root === null) return res;

    while(queue.length !== 0) {
        var level = [];
        var length = queue.length;
        for(var i = 0; i < length; i++) {
            var node = queue.shift();
            level.push(node.val)
            for(var item of node.children) {
                item && queue.push(item);
            }
        }
        res.push(level);
    }
    return res;
};

[leetcode] 515. 在每个树行中找最大值

image.png

此题类似于637.二叉树的层平均值,只是每一层收集的内容变成了最大值。

代码:

/** * Definition for a binary tree node. * function TreeNode(val, left, right) { *     this.val = (val===undefined ? 0 : val) *     this.left = (left===undefined ? null : left) *     this.right = (right===undefined ? null : right) * } */
/** * @param {TreeNode} root
 * @return {number[]} */
var largestValues = function(root) {
    var res = [], queue = [];
    queue.push(root);
    if(root === null) return res;

    while(queue.length !== 0) {
        const length = queue.length;
        const list = [];
        for(var i = 0; i < length; i++) {
            var node = queue.shift();
            list.push(node.val);
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        res.push(Math.max(...list));
    }
    return res;
};

[leetcode] 116. 填充每个节点的下一个右侧节点指针

image.png

此题无需重新组装新的返回内容,只需要重新组建root中的每一个节点即可,每一个TreeNode默认的nextnull,根据题目要求,在每一层所有节点,我们对除了最右节点以外的所有节点添加一个next属性即可,根据队列先进先出的原则,next的值就是queue[0],也就是队列的首项。

代码:

/** * // Definition for a Node. * function Node(val, left, right, next) { *    this.val = val === undefined ? null : val; *    this.left = left === undefined ? null : left; *    this.right = right === undefined ? null : right; *    this.next = next === undefined ? null : next; * }; */

/** * @param {Node} root
 * @return {Node} */
var connect = function(root) {
    var queue = [root];
    if(root === null) return root;
    while(queue.length) {
        const length = queue.length;
        for(var i = 0; i < length; i++) {
            var node = queue.shift();
            if(i < length - 1) {
                node.next = queue[0];
            }
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
    }
    return root;
};

[leetcode] 117. 填充每个节点的下一个右侧节点指针II

image.png

此题与116. 填充每个节点的下一个右侧节点指针 类似,直接上代码。

代码:

/** * // Definition for a Node. * function Node(val, left, right, next) { *    this.val = val === undefined ? null : val; *    this.left = left === undefined ? null : left; *    this.right = right === undefined ? null : right; *    this.next = next === undefined ? null : next; * }; */

/** * @param {Node} root
 * @return {Node} */
var connect = function(root) {
   if (root === null) {
        return null;
    }
    let queue = [root];
    while (queue.length > 0) {
        let n = queue.length;
        for (let i=0; i<n; i++) {
            let node = queue.shift();
            if (i < n-1) node.next = queue[0];
            if (node.left != null) queue.push(node.left);
            if (node.right != null) queue.push(node.right);
        }
    }
    return root;
};

[leetcode] 104. 二叉树的最大深度

image.png

此题比较简单,只需要在遍历的过程中不断记录height即可,当层序遍历结束,返回height就解决了。

/** * Definition for a binary tree node. * function TreeNode(val, left, right) { *     this.val = (val===undefined ? 0 : val) *     this.left = (left===undefined ? null : left) *     this.right = (right===undefined ? null : right) * } */
/** * @param {TreeNode} root
 * @return {number} */
var maxDepth = function(root) {
    if(root === null) return root;
    var queue = [root];
    var height = 0;
    while(queue.length > 0) {
        const length = queue.length;
        height++;
        for(var i = 0; i < length; i++) {
            var node = queue.shift();
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
    }
    return height
};

[leetcode] 111. 二叉树的最小深度

image.png

此题与104. 二叉树的最大深度类似,区别在于需要提前结束循环,通过判断树节点是否满足node.left === null && node.right === null,就可以知道二叉树的最小深度是哪个节点,将该节点遍历时的height返回即可。

代码:

/** * Definition for a binary tree node. * function TreeNode(val, left, right) { *     this.val = (val===undefined ? 0 : val) *     this.left = (left===undefined ? null : left) *     this.right = (right===undefined ? null : right) * } */
/** * @param {TreeNode} root
 * @return {number} */
var minDepth = function(root) {
    if(root === null) return root;
    var queue = [root];
    var height = 0;

    while(queue.length > 0) {
        const n = queue.length;
        height++;
        for(var i = 0; i < n; i++) {
            var node = queue.shift();
            if(node.left === null && node.right === null ) {
                return height;
            }
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
    }
    return height;
};

结尾

二叉树的层序遍历本质上是队列+广度优先遍历的结合运用诞生出来的"神器",此时就会发现通过它可以解决leetcode中很多二叉树的题目!

写代码使我快乐

20 声望
1 粉丝
0 条评论
推荐阅读
JavaScript刷LeetCode拿offer-双指针
一、前言  一般情况下,遍历数组(或者字符串)操作,都是采用单指针从前往后或者从后往前依次访问数组(或者字符串)中的元素。  而对于以下情况,只采用单指针处理,则会徒增时间复杂度和空间复杂度:例如:找...

hellocoder2028阅读 173

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城31阅读 7.1k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco20阅读 2k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5.6k评论 10

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.6k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 19.8k评论 9

写代码使我快乐

20 声望
1 粉丝
宣传栏