力扣解题-199. 二叉树的右视图
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例 1:
输入:root = [1,2,3,null,5,null,4]
输出:[1,3,4]
示例 2:
输入:root = [1,2,3,4,null,null,null,5]
输出:[1,3,4,5]
示例 3:
输入:root = [1,null,3]
输出:[1,3]
示例 4:
输入:root = []
输出:[]
提示:
二叉树的节点个数的范围是 [0,100]
-100 <= Node.val <= 100
Related Topics
树、深度优先搜索、广度优先搜索、二叉树
第一次解答
解题思路
核心方法:广度优先搜索(BFS)层级遍历优化法,基于层序遍历的核心逻辑,优先遍历每一层的右子节点,且仅记录每一层的第一个节点值(即右侧能看到的节点),时间复杂度O(n)、空间复杂度O(n),是本题直观且高效的解法。
核心逻辑拆解
二叉树右视图的核心是“获取每一层最右侧的节点值”,关键在于调整层序遍历的节点入队顺序并精准筛选目标节点:
- 空树处理:若根节点
root == null,直接返回空列表(无任何节点可见); - 初始化:创建结果列表
res存储右视图节点值,队列queue存储待遍历节点,先将根节点入队; - 层级遍历循环:队列非空时,持续处理每一层:
- 记录层大小:遍历前获取队列长度
size(即当前层的节点数); - 遍历当前层节点:通过倒序循环(
i>0)处理当前层所有节点,核心逻辑:- 若
i == size(即当前是该层第一个处理的节点),将节点值加入结果列表(这是该层最右侧节点); - 调整入队顺序:先将节点的右子节点入队,再入队左子节点(保证下一层遍历从右到左,第一个处理的就是最右侧节点);
- 若
- 记录层大小:遍历前获取队列长度
- 返回结果:遍历完所有层后,返回存储右视图节点值的列表。
具体步骤(以示例1 root=[1,2,3,null,5,null,4]为例)
| 遍历层级 | 队列初始状态 | 层大小size | 关键操作(i=size时记录值) | 队列最终状态 | 结果列表更新 |
|---|---|---|---|---|---|
| 0(根层) | [1] | 1 | i=1 → 记录1 | [3,2] | [1] |
| 1 | [3,2] | 2 | i=2 → 记录3 | [4,5] | [1,3] |
| 2 | [4,5] | 2 | i=2 → 记录4 | [] | [1,3,4] |
关键细节说明
- 入队顺序优化:先右后左的入队顺序,确保每一层第一个被处理的节点就是最右侧节点,无需遍历完该层所有节点再找最后一个;
- 筛选条件:
i == size是核心筛选逻辑,仅记录每一层第一个处理的节点值,避免冗余存储; - 边界处理:单独处理空树场景,符合题目中“空树返回空列表”的要求;
- 鲁棒性:即使某层只有左节点(无右节点),也能正确记录该节点为右视图节点(如某层仅节点2,会被正常记录)。
性能说明
- 时间复杂度:O(n)(每个节点仅入队/出队一次,n为节点总数);
- 空间复杂度:O(n)(最坏情况队列存储一层所有节点,如完全二叉树最后一层有n/2个节点);
- 优势:
- 基于经典层序遍历改造,逻辑直观,易理解和维护;
- 无需遍历完层所有节点再筛选,提前记录目标值,效率无冗余;
- 代码简洁,仅需调整入队顺序和筛选条件即可实现需求。
public List<Integer> rightSideView(TreeNode root) {
if(root==null) {
return new ArrayList<>();
}
List<Integer> res=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
//都是取第一个做为res里面的值
int size=queue.size();
for(int i=size;i>0;i--){
TreeNode node=queue.poll();
if(i==size) {
res.add(node.val);
}
if(node.right!=null){
queue.offer(node.right);
}
if(node.left!=null){
queue.offer(node.left);
}
}
}
return res;
}
示例解答
解题思路
解法1:传统BFS层序遍历法(取每层最后一个节点)
核心方法:沿用标准层序遍历逻辑(先左后右入队),遍历完每一层所有节点后,记录该层最后一个节点值,逻辑更通用,无需调整入队顺序。
代码实现
public List<Integer> rightSideView(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<Integer> res = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
TreeNode lastNode = null;
// 遍历当前层所有节点,记录最后一个节点
for (int i = 0; i < size; i++) {
lastNode = queue.poll();
// 先左后右入队(标准层序遍历)
if (lastNode.left != null) {
queue.offer(lastNode.left);
}
if (lastNode.right != null) {
queue.offer(lastNode.right);
}
}
// 记录当前层最后一个节点值(右侧可见)
res.add(lastNode.val);
}
return res;
}
核心逻辑说明
- 标准层序遍历:保持先左后右的节点入队顺序,符合常规层序遍历习惯;
- 记录最后节点:遍历层内所有节点时,用
lastNode记录最后一个弹出的节点(即该层最右侧节点); - 存储结果:每层遍历结束后,将
lastNode.val加入结果列表。
性能说明
- 时间复杂度:O(n)(与原解法一致);
- 空间复杂度:O(n)(与原解法一致);
- 优势:
- 逻辑更通用,无需调整入队顺序,新手易理解;
- 可无缝拓展为“左视图”(记录每层第一个节点);
- 劣势:需遍历完层内所有节点才能确定目标值,无提前终止的优化,但整体效率无本质差异。
解法2:DFS递归法(优先遍历右子树)
核心方法:深度优先搜索,优先遍历右子树,首次访问某一层级时记录节点值(即为该层最右侧节点),时间复杂度O(n)、空间复杂度O(h)(h为树的高度)。
代码实现
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
// 从根节点、层级0开始递归,优先遍历右子树
dfs(root, 0, res);
return res;
}
private void dfs(TreeNode node, int level, List<Integer> res) {
if (node == null) {
return;
}
// 首次访问该层级,记录节点值(优先右子树,故为最右侧节点)
if (level == res.size()) {
res.add(node.val);
}
// 优先递归右子树,再递归左子树
dfs(node.right, level + 1, res);
dfs(node.left, level + 1, res);
}
核心逻辑说明
- 递归参数:
level表示当前节点的层级(根节点为0),res存储右视图节点值; - 层级判断:当
level == res.size()时,说明是首次访问该层级,此时的节点是该层最右侧节点(因优先遍历右子树); - 遍历顺序:先递归右子树,再递归左子树,确保首次访问某层级的节点是最右侧节点。
性能说明
- 时间复杂度:O(n)(每个节点仅被访问一次);
- 空间复杂度:O(h)(递归栈深度等于树的高度,平衡树为O(logn),斜树为O(n));
- 优势:
- 无需使用队列,空间复杂度在平衡树场景下优于BFS;
- 代码更简洁,递归逻辑贴合“找最右侧节点”的语义;
- 劣势:
- 逻辑稍抽象,依赖递归栈,极端斜树场景可能导致栈溢出;
- 无法提前终止遍历,需访问所有节点。
总结
- BFS优先右子节点法(第一次解答):O(n)时间+O(n)空间,提前记录目标值,效率无冗余,是本题的工程优选解法;
- 标准BFS层序遍历法:O(n)时间+O(n)空间,逻辑通用易理解,适合新手入门;
- DFS递归法:O(n)时间+O(h)空间,无需队列,平衡树场景下空间更优;
- 关键技巧:
- 核心思想:右视图的本质是“获取每一层最右侧节点值”,BFS通过入队顺序/层末节点筛选,DFS通过优先遍历右子树+层级标记实现;
- 顺序优化:BFS中先右后左入队可提前记录目标值,减少冗余判断;
- 边界处理:必须单独处理空树场景,返回空列表符合题目要求。