LeetCode二叉树相关题目,199. 二叉树的右视图算是一道经典的层序遍历应用题——难度不算高,但能很好地检验对二叉树层次遍历的理解和灵活运用,今天就来详细拆解这道题,从题目分析到代码实现,再到细节注意点,帮大家吃透这道题的核心逻辑。
一、题目解读:什么是二叉树的右视图?
题目描述很简洁:给定二叉树的根节点root,想象自己站在二叉树的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
举个简单的例子帮助理解:
如果二叉树是:
1
/ \
2 3
\ \
5 4
站在右侧看,能看到的节点依次是 1(顶层)、3(第二层最右)、4(第三层最右),所以返回结果是 [1,3,4]。
核心需求提炼:获取二叉树每一层的最右侧节点值,按层序(从上到下)排列。这就很自然地联想到了二叉树的层序遍历(广度优先搜索,BFS)——因为层序遍历本身就是按层处理节点,只要在处理每一层时,记录下该层的最后一个节点值,就能得到右视图。
二、解题思路:层序遍历+记录每层最右节点
整体思路分3步,清晰易懂,新手也能快速跟上:
-
边界处理:如果根节点为空(空树),直接返回空数组,因为没有任何节点可以看到。
-
初始化工具:用一个队列(queue)存储当前层的节点(层序遍历的核心工具,遵循“先进先出”原则);用一个结果数组(result)存储每一层的最右节点值。
-
层序遍历核心逻辑:
-
循环处理队列,直到队列为空(所有层都处理完);
-
每次循环开始前,获取当前队列的长度(即当前层的节点个数,记为size)——这一步是关键,因为队列在循环中会动态添加下一层的节点,通过size可以区分“当前层节点”和“下一层节点”;
-
遍历当前层的所有节点(循环size次):每次取出队列头部的节点,判断该节点是否是当前层的最后一个节点(i === size - 1),如果是,就将其值加入result;
-
将当前节点的左、右子节点(如果存在)依次加入队列,为下一层的遍历做准备(注意:先加左子节点,再加右子节点,不影响“取最右节点”,因为无论左右顺序如何,当前层的最后一个节点始终是最右的)。
-
补充说明:为什么不用深度优先搜索(DFS)?其实DFS也能实现(比如先遍历右子树、再遍历左子树,记录每一层第一次访问到的节点),但层序遍历的思路更贴合题目“按层取最右”的需求,代码更直观,新手更容易理解和编写,出错率也更低。所以优先选择层序遍历的方案。
三、完整代码实现(TypeScript)
题目中已经给出了TreeNode的定义,这里直接沿用,代码完整可运行,复制到LeetCode即可通过,关键步骤添加了详细注释,方便大家理解每一行的作用:
// 二叉树节点定义(题目已给出,可直接复用)
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 rightSideView(root: TreeNode | null): number[] {
// 边界处理:空树直接返回空数组
if (!root) return [];
const queue = [root]; // 队列存储当前层节点,初始存入根节点
const result: number[] = []; // 存储右视图节点值
// 层序遍历:循环处理每一层
while (queue.length) {
const size = queue.length; // 当前层的节点个数
// 遍历当前层的所有节点
for (let i = 0; i < size; i++) {
const node = queue.shift(); // 取出队列头部节点(先进先出)
if (!node) continue; // 防止空节点(理论上不会出现,兜底处理)
// 记录当前层最右节点(i等于size-1时,是当前层最后一个节点)
if (i === size - 1) {
result.push(node.val);
}
// 将当前节点的左、右子节点加入队列,为下一层遍历做准备
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
}
return result; // 返回右视图结果
};
四、代码细节注意点(避坑指南)
这道题看似简单,但新手很容易在几个细节上出错,这里重点提醒3个关键避坑点:
-
size的取值时机:必须在“处理当前层节点”的循环开始前获取size,而不是在循环中获取。因为循环中会执行queue.shift()(删除头部节点)和queue.push()(添加子节点),队列长度会动态变化,若在循环中获取size,会导致遍历次数错误,无法正确区分当前层和下一层。
-
queue.shift()的注意事项:在TypeScript中,数组的shift()方法会删除并返回数组的第一个元素,同时修改数组长度,正好符合队列“先进先出”的需求。但要注意,shift()返回的值可能为undefined(当队列为空时),所以添加了“if (!node) continue”的兜底处理,避免报错。
-
子节点的添加顺序:代码中是“先加左子节点,再加右子节点”,有同学会疑惑“是不是应该先加右子节点?”。其实不需要——因为我们是通过“遍历当前层所有节点,取最后一个”来获取最右节点,无论左、右子节点的添加顺序如何,当前层的最后一个节点始终是最右的,添加顺序不影响最终结果。但如果是用DFS实现,就需要先遍历右子节点。
五、复杂度分析(面试常问)
刷题不仅要会写代码,还要能分析复杂度,这也是面试中常考的点,这里简单分析一下:
-
时间复杂度:O(n),其中n是二叉树的节点总数。因为每个节点都会被加入队列一次,也会被取出队列一次,每个节点的处理时间是O(1),所以整体时间复杂度是O(n)。
-
空间复杂度:O(n),最坏情况下(满二叉树),最后一层的节点个数是n/2(向下取整),队列中最多会存储n/2个节点,所以空间复杂度是O(n);最好情况下(二叉树是一条链),队列中最多存储1个节点,空间复杂度是O(1),但整体最坏情况是O(n),所以通常说空间复杂度是O(n)。
六、总结与拓展
这道题的核心是层序遍历的灵活运用——本质上是“层序遍历+记录每层最后一个节点”,思路简单,代码直观,非常适合新手练习二叉树的层序遍历。
拓展思考:如果题目改成“二叉树的左视图”,该如何修改代码?其实很简单,只需要将“记录当前层最后一个节点”改成“记录当前层第一个节点”(即i === 0时,将node.val加入result),其余代码完全不变,大家可以自己动手试试,加深理解。