【LeetCode Hot100 刷题日记 (45/100)】199. 二叉树的右视图 —— 层序遍历 & DFS 的巧妙应用🌳

0 阅读5分钟

📌 题目链接:199. 二叉树的右视图 - 力扣(LeetCode)

🔍 难度:中等 | 🏷️ 标签:树、深度优先搜索(DFS)、广度优先搜索(BFS)、二叉树

⏱️ 目标时间复杂度:O(n)

💾 空间复杂度:O(h)(DFS)或 O(w)(BFS),其中 h 是树高,w 是最大层宽


🧠题目分析

给定一棵二叉树的根节点 root,要求返回从右侧视角看到的节点值,即每层最右边的节点,自上而下组成一个数组。

这道题的本质不是“一路向右走”,而是按层取最右节点。例如:

  • 示例 1:[1,2,3,null,5,null,4] → 每层最右为 [1,3,4]
  • 示例 2:[1,null,3] → 只有两层,结果为 [1,3]

⚠️ 常见误区:直接 while(root) { ans.push(root->val); root = root->right; } —— 这是错误的!因为右子树可能为空,但左子树更深,此时右视图仍需包含左子树中的节点(如示例 2 的变种)。


🔑核心算法及代码讲解

本题有两种主流解法:广度优先搜索(BFS)深度优先搜索(DFS) 。两者时间复杂度均为 O(n),但空间和实现思路不同。

✅ 方法一:广度优先搜索(BFS)—— 层序遍历取末尾

这是最直观的方法。利用队列进行层序遍历,每层只保留最后一个出队的节点值

// BFS 解法:层序遍历,每层取最后一个节点
vector<int> rightSideView(TreeNode* root) {
    queue<TreeNode*> que;
    if (root != nullptr) que.push(root);
    vector<int> result;

    while (!que.empty()) {
        int size = que.size();                // 当前层的节点数
        for (int i = 0; i < size; ++i) {
            TreeNode* node = que.front();
            que.pop();

            if (i == size - 1) {              // 👈 关键:当前是该层最后一个节点
                result.push_back(node->val);
            }

            if (node->left)  que.push(node->left);
            if (node->right) que.push(node->right);
        }
    }
    return result;
}

优点:逻辑清晰,易于理解,面试时可快速写出。
适用场景:需要按层处理的问题(如层平均值、每层最大值等)。


✅ 方法二:深度优先搜索(DFS)—— 先右后左,首次访问即答案

DFS 的核心思想是:按“根 → 右 → 左”顺序遍历,这样每一层第一个被访问到的节点,就是该层最右边的节点

我们用一个 result 数组记录每层的答案。当递归到某一层 depth 时,若 result.size() == depth,说明这是该层第一次被访问,当前节点就是右视图所需节点。

// DFS 解法:先遍历右子树,再左子树
void dfs(TreeNode* node, int depth, vector<int>& result) {
    if (!node) return;

    if (depth == result.size()) {             // 👈 关键:该深度首次访问
        result.push_back(node->val);
    }

    dfs(node->right, depth + 1, result);      // 先右!
    dfs(node->left,  depth + 1, result);      // 再左
}

vector<int> rightSideView(TreeNode* root) {
    vector<int> result;
    dfs(root, 0, result);
    return result;
}

优点:空间更优(递归栈深度 = 树高 h,通常 h << n)
面试加分点:展示对 DFS 遍历顺序的灵活控制能力


🧩解题思路(分步骤)

BFS 思路步骤:

  1. 初始化队列,将根节点入队(若非空)

  2. 当队列非空时,记录当前层节点数量 size

  3. 循环 size 次:

    • 出队一个节点
    • 若是该层最后一个(i == size - 1),加入结果
    • 将其左右子节点(若存在)入队
  4. 返回结果数组

DFS 思路步骤:

  1. 从根节点开始,深度 depth = 0
  2. 若当前深度等于结果数组长度,说明首次到达该层,加入当前节点值
  3. 先递归右子树,再递归左子树(顺序不能反!)
  4. 递归终止条件:节点为空

📊算法分析

方法时间复杂度空间复杂度适用性
BFSO(n)O(w)w 为最大层宽,最坏 O(n)(完全二叉树底层)
DFSO(n)O(h)h 为树高,最坏 O(n)(退化为链表),平均 O(log n)

💡 面试建议

  • 如果面试官问“能否用 DFS?”,一定要能写出先右后左的版本。
  • 可对比两种方法:“BFS 更直观,DFS 更省空间”。
  • 强调:右视图 ≠ 一直走 right 指针,这是常见陷阱!

💻代码

✅ C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

// ==================== BFS 解法 ====================
vector<int> rightSideView_BFS(TreeNode* root) {
    queue<TreeNode*> que;
    if (root != nullptr) que.push(root);
    vector<int> result;

    while (!que.empty()) {
        int size = que.size();                // 当前层的节点数
        for (int i = 0; i < size; ++i) {
            TreeNode* node = que.front();
            que.pop();

            if (i == size - 1) {              // 👈 当前是该层最后一个节点
                result.push_back(node->val);
            }

            if (node->left)  que.push(node->left);
            if (node->right) que.push(node->right);
        }
    }
    return result;
}

// ==================== DFS 解法 ====================
void dfs(TreeNode* node, int depth, vector<int>& result) {
    if (!node) return;

    if (depth == result.size()) {             // 👈 该深度首次访问
        result.push_back(node->val);
    }

    dfs(node->right, depth + 1, result);      // 先右!
    dfs(node->left,  depth + 1, result);      // 再左
}

vector<int> rightSideView_DFS(TreeNode* root) {
    vector<int> result;
    dfs(root, 0, result);
    return result;
}

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 构建示例1: [1,2,3,null,5,null,4]
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->right = new TreeNode(5);
    root->right->right = new TreeNode(4);

    vector<int> ans1 = rightSideView_BFS(root);
    vector<int> ans2 = rightSideView_DFS(root);

    // 输出应为 [1, 3, 4]
    for (int x : ans1) cout << x << " ";
    cout << "\n";
    for (int x : ans2) cout << x << " ";
    cout << "\n";

    return 0;
}

✅ JavaScript

// JavaScript 版本(BFS)
var rightSideView = function(root) {
    if (!root) return [];
    let res = [], queue = [root];
    
    while (queue.length > 0) {
        let size = queue.length;
        for (let i = 0; i < size; i++) {
            let node = queue.shift();
            if (i === size - 1) {          // 👈 该层最后一个
                res.push(node.val);
            }
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
    }
    return res;
};

// JavaScript 版本(DFS)
var rightSideView = function(root) {
    const res = [];
    function dfs(node, depth) {
        if (!node) return;
        if (depth === res.length) {        // 👈 首次到达该层
            res.push(node.val);
        }
        dfs(node.right, depth + 1);        // 先右!
        dfs(node.left, depth + 1);         // 再左
    }
    dfs(root, 0);
    return res;
};

🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!