LeetCode 637. 二叉树的层平均值:BFS层序遍历实战解析

LeetCode简单难度的二叉树题目------637. 二叉树的层平均值,这道题虽然难度不高,但非常经典,是入门二叉树层序遍历的绝佳练习题,适合刚接触二叉树层级操作的小伙伴上手,话不多说,我们直接开始!

一、题目解读

先来看题目要求:给定一个非空二叉树的根节点root,以数组的形式返回每一层节点的平均值。并且说明,与实际答案相差10的-5次方以内的答案都可以被接受。

举个简单的例子帮助理解:如果二叉树是 [3,9,20,null,null,15,7],那么每一层的节点分别是:

  • 第1层:[3] → 平均值 3.0

  • 第2层:[9,20] → 平均值 (9+20)/2 = 14.5

  • 第3层:[15,7] → 平均值 (15+7)/2 = 11.0

最终返回的数组就是 [3.0, 14.5, 11.0]。

核心需求很明确:遍历二叉树的每一层,计算该层所有节点值的总和,再除以该层节点个数,将结果依次存入数组并返回

二、解题思路分析

要解决"按层操作二叉树"的问题,最常用、最直观的方法就是 层序遍历(广度优先搜索,BFS)

层序遍历的核心思想是:使用一个队列(queue)来维护当前层的所有节点,每次处理完当前层的所有节点后,再依次将下一层的节点加入队列,循环往复,直到队列为空(所有层都处理完毕)。

对应到这道题,我们可以按照以下步骤实现:

  1. 边界处理:如果根节点root为空,直接返回空数组(题目说非空,但代码需考虑鲁棒性)。

  2. 初始化:创建一个队列queue,将根节点root加入队列;创建一个结果数组result,用于存储每一层的平均值。

  3. 循环处理每一层:只要队列不为空,就执行以下操作:

    • 记录当前层的节点个数levelSize(即队列当前的长度),因为队列中此时存的都是当前层的节点。

    • 初始化当前层的总和sum为0,用于累加当前层所有节点的值。

    • 遍历当前层的所有节点:循环levelSize次,每次从队列头部取出一个节点,将其值加入sum,然后判断该节点是否有左、右子节点,若有则依次加入队列(作为下一层的节点)。

    • 计算当前层的平均值(sum / levelSize),将其加入结果数组result。

  4. 循环结束后,返回结果数组result。

这里有两个小细节需要注意:

  • 队列的操作:使用shift()方法取出队列头部元素(符合先进先出的特性),push()方法加入队列尾部。

  • 精度问题:题目允许与实际答案相差10的-5次方以内,因此直接用普通除法计算即可,无需额外处理精度(JavaScript/TypeScript中number类型为浮点型,足以满足需求)。

三、完整代码实现(TypeScript)

题目中已经给出了TreeNode的类定义,我们直接基于此编写解题函数即可,完整代码如下(带详细注释):

typescript 复制代码
// 二叉树节点类定义(题目已给出,无需修改)
class TreeNode {
  val: number
  left: TreeNode | null
  right: TreeNode | null
  constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
    this.val = (val === undefined ? 0 : val) // 节点值默认0
    this.left = (left === undefined ? null : left) // 左子节点默认null
    this.right = (right === undefined ? null : right) // 右子节点默认null
  }
}

// 解题函数
function averageOfLevels(root: TreeNode | null): number[] {
  // 边界处理:根节点为空,返回空数组
  if (!root) return [];
  
  const queue = [root]; // 队列:存储当前层的节点,初始加入根节点
  const result: number[] = []; // 结果数组:存储每一层的平均值
  
  // 循环处理每一层,直到队列为空
  while (queue.length) {
    const levelSize = queue.length; // 当前层的节点个数
    let sum = 0; // 当前层的节点值总和
    
    // 遍历当前层的所有节点
    for (let i = 0; i < levelSize; i++) {
      const node = queue.shift()!; // 取出队列头部节点(非空断言,因levelSize控制循环次数)
      sum += node.val; // 累加节点值到sum
      
      // 若有左子节点,加入队列(下一层节点)
      if (node.left) queue.push(node.left);
      // 若有右子节点,加入队列(下一层节点)
      if (node.right) queue.push(node.right);
    }
    
    // 计算当前层平均值,加入结果数组
    result.push(sum / levelSize);
  }
  
  return result; // 返回最终结果
};

四、代码细节拆解

可能有小伙伴对代码中的一些细节有疑问,这里逐一拆解说明:

1. 非空断言(!)的使用

代码中 queue.shift()! 后面的感叹号是TypeScript的非空断言,表示我们确定shift()方法返回的不是null或undefined。因为我们通过levelSize控制了循环次数,而levelSize等于队列初始长度,循环中每次shift()都会取出一个节点,不会出现取空的情况,因此可以安全使用非空断言。

2. 队列的作用

队列在这里起到了"分层缓存"的作用:每次循环开始时,队列中存储的是当前层的所有节点,循环结束时,队列中存储的是下一层的所有节点,这样就能保证我们每次处理的都是同一层的节点。

3. 时间复杂度与空间复杂度

  • 时间复杂度:O(n),其中n是二叉树的节点总数。因为我们需要遍历每一个节点一次,每个节点入队和出队各一次,操作都是O(1)的。

  • 空间复杂度:O(m),其中m是二叉树中某一层的最大节点个数。队列最多会存储某一层的所有节点,最坏情况下(完全二叉树的最后一层),m = n/2,因此空间复杂度为O(n)。

五、易错点提醒

这道题虽然简单,但新手很容易踩以下两个坑,大家可以留意一下:

  1. 忘记记录当前层的节点个数(levelSize),直接在循环中使用queue.length。这样会导致循环次数出错,因为遍历过程中会不断向队列中加入下一层的节点,queue.length会动态变化,无法正确遍历当前层。

  2. 边界处理遗漏:虽然题目说"非空二叉树",但代码中如果不判断root是否为空,当传入null时会报错(queue.shift()会返回undefined,后续访问node.val会报错),因此必须加上if (!root) return [];

六、总结

这道题的核心是考察二叉树的层序遍历,解题思路清晰,代码实现也比较简洁。通过这道题,我们可以熟练掌握层序遍历的基本框架,后续遇到"二叉树的层和""二叉树的层最大值"等类似的按层操作题目,都可以沿用这个思路解决。

其实二叉树的层序遍历就像"剥洋葱",一层一层往里面剥,只要抓住"队列维护当前层节点"这个核心,就能轻松应对这类题目。

相关推荐
敲敲了个代码1 小时前
浏览器时间管理大师:深度拆解 5 大核心调度 API
前端·javascript·学习·web
ssshooter1 小时前
看完就懂 useLayoutEffect
前端·react.js·面试
parade岁月1 小时前
DOM 里有 Tailwind class,为什么样式还是不生效?v4 闭环修复实战
前端·vue.js
ashuicoder1 小时前
vue文件自动生成路由会成为主流
前端·vue.js
白中白121381 小时前
Vue系列-4
前端·javascript·vue.js
Ai runner1 小时前
Show call stack in perfetto from json input
java·前端·json
晴殇i1 小时前
前端防调试攻防战:如何保护你的JavaScript代码不被“偷窥”?
前端·javascript·面试
清粥油条可乐炸鸡2 小时前
tailwind-variants基本使用
前端·css