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)来维护当前层的所有节点,每次处理完当前层的所有节点后,再依次将下一层的节点加入队列,循环往复,直到队列为空(所有层都处理完毕)。
对应到这道题,我们可以按照以下步骤实现:
-
边界处理:如果根节点root为空,直接返回空数组(题目说非空,但代码需考虑鲁棒性)。
-
初始化:创建一个队列queue,将根节点root加入队列;创建一个结果数组result,用于存储每一层的平均值。
-
循环处理每一层:只要队列不为空,就执行以下操作:
-
记录当前层的节点个数levelSize(即队列当前的长度),因为队列中此时存的都是当前层的节点。
-
初始化当前层的总和sum为0,用于累加当前层所有节点的值。
-
遍历当前层的所有节点:循环levelSize次,每次从队列头部取出一个节点,将其值加入sum,然后判断该节点是否有左、右子节点,若有则依次加入队列(作为下一层的节点)。
-
计算当前层的平均值(sum / levelSize),将其加入结果数组result。
-
-
循环结束后,返回结果数组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)。
五、易错点提醒
这道题虽然简单,但新手很容易踩以下两个坑,大家可以留意一下:
-
忘记记录当前层的节点个数(levelSize),直接在循环中使用queue.length。这样会导致循环次数出错,因为遍历过程中会不断向队列中加入下一层的节点,queue.length会动态变化,无法正确遍历当前层。
-
边界处理遗漏:虽然题目说"非空二叉树",但代码中如果不判断root是否为空,当传入null时会报错(queue.shift()会返回undefined,后续访问node.val会报错),因此必须加上
if (!root) return [];。
六、总结
这道题的核心是考察二叉树的层序遍历,解题思路清晰,代码实现也比较简洁。通过这道题,我们可以熟练掌握层序遍历的基本框架,后续遇到"二叉树的层和""二叉树的层最大值"等类似的按层操作题目,都可以沿用这个思路解决。
其实二叉树的层序遍历就像"剥洋葱",一层一层往里面剥,只要抓住"队列维护当前层节点"这个核心,就能轻松应对这类题目。