5

本专题旨在分享刷Leecode过程发现的一些思路有趣或者有价值的题目。【当然是基于js进行解答】。
这道题应该算是二叉树的基础题,建议还是学一下,不难且经典

题目相关

  • 原题地址: https://leetcode-cn.com/probl...
  • 题目描述:

    从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行,例如,给定二叉树: [3,9,20,null,null,15,7]
       3
       / \
      9  20
        /  \
       15   7

    返回其层次遍历结果:

    [
      [3],
      [9,20],
      [15,7]
    ]

    Tips

    考虑某些同学可能比较少用js来刷leetcode,我们在这里简单介绍下,关于树类型的数据输入,在js里的表示。 形如上题中的内容,给定二叉树的输入,其实并非一个数组,而应该是如下所示: 每个节点是一个object

      const root = {
        val: 3,
        left: { // left表示当前节点的左侧子节点
          val: 9,
          left: null, // 对照上图可以看到 节点9没有左右子节点
          right: null,
        },
        right: {
          val: 20,
          left: {
            val: 15, // 对照上图可以看到 节点15没有左右子节点
            left: null, 
            right: null,
          }, 
          right: {
            val: 7, // 对照上图可以看到 节点7没有左右子节点
            left: null, 
            right: null,
          },
        }
      }

思路解析

这道题比较基础,其实考的是BFS+层序遍历,怎么说呢?
划重点.png

首先 BFS(Breadth First Search)也就是广度优先搜索, 指的是在遍历过程,每次总是优先遍历当前节点的所有邻接节点,将其放入一个队列,比如上题的例子:

image.png

  • step1: 首先准备一个空队列(也就是数组) [ ] ,并且把根节点放入数组中,也就是 [3]
  • step2: 先取出队列的最前面节点(此时队列只有节点3,取出之后队列置空), 然后把它的所有邻接节点(在二叉树里面也就是左右子节点)按顺序加入访问队列,所以此时队列元素为 [9  20] ,
  • step3: 继续按照第二步的规则,取出节点9,并把9的子节点放入队列(无子节点);再把节点20取出,并且把节点20的子节点存入队列,此时队列剩余元素为 [15, 7] ;
  • step4: 继续按照第二步规则, 取出节点15, 并把子节点放入队列(无子节点);再把节点7取出,并且把节点7的子节点存入队列,此时队列剩余元素为 [], 队列为空 结束整个过程 ;

仔细观察以上的步骤,你发现了吗?
image.png

  1. 除了第一步初始化数据,后续的所有步骤 都在按照第2步的规则重复
  2. 观察每一步的取出结果:

    1. step2, 取出[3];
    2. step3, 取出[9, 20];
    3. step4, 取出[15, 17];

    这里每一步的取出结果 正好就是题设要求的每一层遍历的结果。

  3. 整个过程结束条件,就是节点队列为空。
    这道题讲到这里,其实大致思路就已经很清晰了,或者说,其实上面的步骤就已经是伪代码了(没办法,因为题目本身就非常典型-_-!)
  • 首先第2步的规则肯定就是主函数,因为它全程一直循环;
  • 其次,初始条件/结束条件分别对应的是 根节点入队列和队列为空;
  • 唯一的难点,在于如何优雅地处理【分层输出】,而这一点 我建议大家直接看我最后贴地完整代码。
    image.png

完整代码

那么其实就可以直接看代码了:

var levelOrder = function(root) {
    if (!root) { // 如果根节点都不存在 那直接结束 
        return [];
    }

    const nodes = [root]; // 初始化,对应前面分析里地初始条件,
    const res = []; 

    while(nodes.length > 0) { // 外层循环 当节点队列为空时结束
        const tempRes = [];
        let isEmpty = false;

        // 【注意点】这个内循环 也就是处理分层输出地精髓所在,大家可以仔细揣摩下这个循环条件
        // 尝试回答这里为什么不采用 “i=0 ;i<nodes.length; i++”的形式
        for(let i = nodes.length;i > 0 ; i--) { 
            const node = nodes.shift();
            tempRes.push(node.val);
            if(node.left) {
                nodes.push(node.left);
            }
            if(node.right) {
                nodes.push(node.right);
            }
        }
        res.push(tempRes);
       
    }
    return res;
 };

好了整体的题目算是基础题, 就不画动态图了(画图很耗时), 但是想两个问题给大家思考:

  1. 也就是代码块里的问题,为什么内循环的条件不是 i=0 ;i<nodes.length; i++ ? 如果是,会怎么样?
  2. 如果本道题演变成输出深度优先遍历的结果,核心要点要怎么改动?
    如果能够回答出上面的两个问题,那基本上就理解了这个知识点!

image.png


安歌
7k 声望5.5k 粉丝

目前就职于Ringcentral厦门,随缘答题, 佛系写文章,欢迎私信探讨.