题目描述

✅ 199. 二叉树的右视图

image-20260928190113821

image-20260928190113822

image-20260928190113823

题意分析

从右侧看二叉树,同一深度只保留最靠右的节点,结果按深度从上到下排列。因此本题求的是每一层最右节点的值,既不是这一层的最大值,也不是所有右孩子的值。

不能只从根节点沿着右孩子走:右侧分支结束后,更深层仍可能存在左子树中的节点,它们在各自层上依然可见。空树返回空列表,其他情况下每个实际存在的层都恰好贡献一个节点。

解法:BFS 层序遍历

核心思路

[!blue]

答案按层产生,可以用队列逐层访问整棵树。每轮开始时,队列中恰好保存当前层的节点,并且从队首到队尾按从左到右排列;只要取出其中最后一个节点,就得到这一层的右视图。

这个顺序来自入队规则:先按从左到右的顺序处理父节点,再把每个父节点的左孩子、右孩子依次加入队尾。更靠左的父节点的孩子先入队,同一父节点的左孩子也在右孩子前面,因此下一层仍然从左到右排列。根节点单独成层,归纳下来,每层都能维持这个顺序。

遍历当前层时还会把下一层节点加入同一个队列,所以必须在本轮开始前记住 levelSize = queue.size(),并且只出队这 levelSize 个节点。这样新加入的孩子留给下一轮,最后一次出队的位置 i == levelSize - 1 才确实属于当前层的最右节点。

解题步骤

  1. 创建结果列表。若根节点为空,直接返回;否则把根节点加入队列。
  2. 每轮记录队列当前长度 levelSize,它就是本层节点数。
  3. 连续取出 levelSize 个节点,只有本层最后一个节点的值加入结果。
  4. 对每个出队节点,按先左后右的顺序将非空孩子加入队尾,使下一层保持从左到右的顺序。
  5. 当前层处理结束后,队列只剩下一层节点;重复以上过程,直到队列为空。

代码实现

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new ArrayList<>();

        if (root == null) {
            return res;
        }

        Queue<TreeNode> queue = new ArrayDeque<>();

        queue.offer(root);

        while (!queue.isEmpty()) {
            // 先固定当前层数量,孩子入队不改变本轮边界。
            int levelSize = queue.size();

            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();

                // 按左到右出队时,最后一个节点就是本层右视图。
                if (i == levelSize - 1) {
                    res.add(node.val);
                }

                if (node.left != null) {
                    queue.offer(node.left);
                }

                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }

        return res;
    }
}
func rightSideView(root *TreeNode) []int {
    res := make([]int, 0)
    if root == nil {
        return res
    }

    queue := []*TreeNode{
        root,
    }
    for len(queue) > 0 {
        // 先固定当前层数量,孩子入队不改变本轮边界。
        levelSize := len(queue)
        for i := 0; i < levelSize; i++ {
            node := queue[0]
            queue = queue[1:]
            // 按左到右出队时,最后一个节点就是本层右视图。
            if i == levelSize-1 {
                res = append(res, node.Val)
            }

            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
    }

    return res
}

复杂度分析

  • 时间复杂度:$O(n)$,n 为节点数,每个节点只入队、出队一次。
  • 空间复杂度:$O(w)$,w 为二叉树最大宽度。队列可能同时保留本层剩余节点和下一层已入队节点,但数量仍为 $O(w)$;不计返回结果。

关键点总结

[!green]

  • 右视图由每层的结构位置决定,与节点数值大小无关。
  • 从左到右处理父节点、先左后右加入孩子,保证下一层也从左到右。
  • 先固定层大小,再处理这一层,避免新入队的孩子干扰当前层的边界。

解法:右优先 DFS

核心思路

[!blue]

另一种做法不显式分层,而是让深度优先遍历优先进入右子树。对于任意固定深度,右子树中处于该深度的节点会早于左子树中的节点被访问;在每个子树内部也遵循相同规则。因此,某一层第一次被访问到的节点,就是这一层最靠右的节点。

用 depth 表示当前节点深度,根节点深度为 0;结果列表按深度保存已找到的右视图。当 depth == res.size() 时,说明前面的层已经有答案,而这一层还没有答案,把当前节点加入结果即可。如果 depth 小于结果长度,说明这一层已经记录了更靠右的节点,不能覆盖。

记录之后,先递归右孩子,再递归左孩子。右子树不够深时,左子树仍会继续向下探索;一旦到达此前没有访问过的深度,就会补上该层答案。因此右优先只是决定同层谁先被看到,并不意味着忽略左子树。

解题步骤

  1. 创建本次调用独立的结果列表,从根节点和深度 0 开始递归。
  2. 当前节点为空时结束该分支;非空时,若 depth 等于结果长度,就记录当前节点值。
  3. 先递归右孩子,再递归左孩子,两次调用的深度都为 depth + 1。
  4. 递归完成后返回结果;节点只会在其所在深度尚无答案时被记录。

代码实现

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        collectRightmost(root, 0, res);
        return res;
    }

    private void collectRightmost(TreeNode node, int depth, List<Integer> res) {
        if (node == null) {
            return;
        }

        if (depth == res.size()) {
            res.add(node.val);
        }

        collectRightmost(node.right, depth + 1, res);
        collectRightmost(node.left, depth + 1, res);
    }
}
func rightSideView(root *TreeNode) []int {
    res := make([]int, 0)
    var dfs func(*TreeNode, int)
    dfs = func(node *TreeNode, depth int) {
        if node == nil {
            return
        }
        if depth == len(res) {
            res = append(res, node.Val)
        }
        dfs(node.Right, depth+1)
        dfs(node.Left, depth+1)
    }
    dfs(root, 0)
    return res
}

复杂度分析

  • 时间复杂度:$O(n)$,每个节点访问一次,记录与深度判断均为常数操作。
  • 空间复杂度:$O(h)$,h 为树高,来自递归调用栈;不计返回结果。平衡树的栈深为 $O(\log n)$,退化为链时为 $O(n)$。

关键点总结

[!green]

  • 右优先 DFS 保证每层第一次访问到的节点最靠右。
  • depth == res.size() 同时判断这一层是否已有答案,并保持结果按深度排列。
  • 两棵子树都需要遍历,左子树负责补上右子树未覆盖的深度。
  • BFS 保存待处理的一层节点,DFS 保存当前递归路径,额外空间分别取决于树宽和树高。

易错点总结

[!yellow]

  • 只沿右孩子向下,会漏掉右侧分支结束后左子树中更深的可见节点;两种遍历都必须覆盖整棵树。
  • BFS 在内层循环中动态读取队列长度,会把当前层与下一层混在一起,无法准确找到本层最后一个节点。
  • BFS 若改成先右后左入队,就应取每层第一个节点;仍取最后一个会得到左视图。
  • DFS 先访问左子树却记录每层第一个节点,会得到左视图;访问顺序必须与记录规则配套。
  • DFS 对同一深度反复覆盖结果,会让较晚访问的左侧节点覆盖已经记录的右侧节点。
  • 空树应直接返回空结果,不能把空节点加入不接受空值的队列。

相似题目

题目 难度 关联与区别
515. 在每个树行中找最大值 中等 同样每层输出一个值,但最大值与最右节点无关,不能混淆选择条件。
513. 找树左下角的值 中等 同样取层的边界节点,原题只保留最深层最左值,本题保留每层最右值。
补充题 198. 由前序和中序遍历求二叉树右视图 中等 都逐层选取最右节点;补充题先依据两种遍历结果重建树。
补充题 196. 二叉树左视图 中等 都按层遍历二叉树;本题取每层最右节点,补充题取最左节点。
102. 二叉树的层序遍历 中等 按层遍历并在同一层内聚合;本题每层只取最右节点,该题保留每层全部节点。
103. 二叉树的锯齿形层序遍历 中等 按层遍历并在同一层内聚合;本题每层只取最右节点,该题交替调整每层输出方向。
107. 二叉树的层序遍历 II 中等 按层遍历并在同一层内聚合;本题每层只取最右节点,该题把自顶向下的层结果反转。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/78753502
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!