二叉树高频算法题全解析:从基础遍历到进阶应用

一、开篇引言

二叉树作为数据结构中的核心内容,不仅是大厂面试高频考点,更是理解复杂树结构、递归思维的基石。无论是刷题入门,还是冲刺算法进阶,二叉树相关题目都是绕不开的 “必修课”。本文整理15 道二叉树经典题,覆盖遍历、特性判断、结构转换、路径问题等核心场景,用清晰思路 + 代码实现带你吃透二叉树!

二、基础遍历与深度优先搜索(DFS)

1. 二叉树的中序遍历(简单)

  • 核心逻辑:中序遍历顺序为 左子树 → 根节点 → 右子树,递归实现简洁直观,迭代实现需借助栈模拟递归调用栈。
    class Solution {
        void process(TreeNode* root,vector<int>&ans){
            if(root == nullptr){
                return;
            }
            process(root->left,ans);//递归左子树
            ans.push_back(root->val);//中序
            process(root->right,ans);//递归右子树    
        }
    public:
        vector<int> inorderTraversal(TreeNode* root) {
            vector<int> ans;
            process(root,ans);
            return ans;
        }
    };

    2. 二叉树的最大深度(简单)

  • 核心逻辑:深度优先搜索,递归计算 max(左子树深度, 右子树深度) + 1(根节点深度为 1 )。
    注:本文的「自底向上」和「自顶向下」,关注的都是深度信息如何在父子之间传递。
    「自底向上」 先[递]到最底 再往上[归]
    class Solution {
    public:
        int maxDepth(TreeNode* root) {
            if(root == nullptr){
                return 0;
            }
            int l_len = maxDepth(root->left);//拿到左数信息
            int r_len = maxDepth(root->right);//拿到右数信息
            return max(l_len,r_len) + 1;//返回左右中最大值 + 1
        }
    };
    
    方法二:自顶向下
    class Solution {
    public:
        int maxDepth(TreeNode* root) {
           int ans = 0 ;
           auto dfs = [&](this auto&& dfs,TreeNode* node,int depth)->void{
                if(node == nullptr){
                    return;
                }
                depth++;
                ans = max(ans,depth);
                dfs(node->left,depth);
                dfs(node->right,depth);
           };
           dfs(root,0);
           return ans;
        }
    };
    复杂度分析
  • 时间复杂度:O(n),其中 n 为二叉树的节点个数。
  • 空间复杂度:O(n)。最坏情况下,二叉树退化成一条链,递归需要 O(n) 的栈空间。
  • 我在网上看到一种理解递归的说法:「在写递归函数时,可以假设递归返回的结果一定是正确的」。其实这种说法本质上就是数学归纳法

   3. 翻转二叉树(简单)

  • 核心逻辑:递归交换每个节点的左右子树,“自顶向下” 完成翻转。
  • 算法
    递归调用 invertTree(root.left),获取到左子树翻转后的结果 left。
    递归调用 invertTree(root.right),获取到右子树翻转后的结果 right。
    交换左右儿子,即更新 root.left 为 right,更新 root.right 为 left。
    返回 root。
    递归边界:如果 root 是空节点,返回空。

    写法一
    class Solution {
    public:
        TreeNode* invertTree(TreeNode* root) {
            if(root == nullptr){
                return nullptr;
            }
            auto left = invertTree(root->left);//拿到左子树翻转后的信息
            auto right = invertTree(root->right);//拿到右子树翻转后的信息
            root->left = right;
            root->right = left;//实现翻转
            return root;
        }
    };
    写法二:也可以先交换左右儿子 再翻转
    class Solution {
    public:
        TreeNode* invertTree(TreeNode* root) {
            if (root == nullptr) {
                return nullptr;
            }
            swap(root->left, root->right); // 交换左右儿子
            invertTree(root->left);        // 翻转左子树
            invertTree(root->right);       // 翻转右子树
            return root;
        }
    };
    
    复杂度分析
    时间复杂度:O(n),其中 n 为二叉树的节点个数。
    空间复杂度:O(n)。最坏情况下,二叉树退化成一条链,递归需要 O(n) 的栈空间。

    4. 对称二叉树(简单)

  • 核心逻辑:判断 “左子树的左” 与 “右子树的右”、“左子树的右” 与 “右子树的左” 是否对称,递归比较两对节点。
    class Solution {
        bool isSameTree(TreeNode*p,TreeNode*q){
            if(p==nullptr || q == nullptr){
                return p==q;
            }
            return p->val == q->val && isSameTree(p->right,q->left) && isSameTree(p->left,q->right);
        }
    public:
        bool isSymmetric(TreeNode* root) {
            return isSameTree(root->right,root->left);
        }
    };

    5. 二叉树的直径(简单)

  • 核心逻辑:直径 = 某节点左右子树深度之和的最大值。需在计算深度时,同步更新全局最大直径。

  • - 链:从子树叶子到当前节点的路径,最长链长为dfs返回值(空节点-1,叶子0)。

  • - 直径:由两条(或一条)链拼成的路径,枚举每个节点,计算其左右链的节点值之和,更新最大答案(可能在任意节点拐弯,不限于根)。

  • - 注意:dfs返回当前子树最大链长(不含当前节点与父节点的边),非直径;若返回直径,后续拼接无效。

    class Solution {
    public:
        int diameterOfBinaryTree(TreeNode* root) {
            int ans = 0;
            auto dfs = [&](this auto&& dfs,TreeNode*node)->int{
                if(node==nullptr){
                    return -1;//对于叶子节点来说,链长就是 -1 + 1 = 0
                }
                auto l_len = dfs(node->left) + 1;//左子树最大链长 + 1
                auto r_len = dfs(node->right)+ 1;//右子树最大链长 + 1
                ans = max(ans,l_len + r_len);//两条链长拼成路径
                return max(l_len,r_len);//当前子树最大链长
            };
            dfs(root);
            return ans;
        }
    };

    递归过程拆解:

  • 从根节点 1 开始调用 dfs (1)

  • 计算左子树(节点 2):调用 dfs (2)

    • 计算节点 2 的左子树(节点 4):调用 dfs (4)
      • 节点 4 的左右子树都是 nullptr
      • 左链长 = dfs (nullptr) + 1 = -1 + 1 = 0
      • 右链长 = dfs (nullptr) + 1 = -1 + 1 = 0
      • 更新 ans = max (0, 0+0) = 0
      • 返回 max (0, 0) = 0(节点 4 的最长链长)
    • 计算节点 2 的右子树(节点 5):调用 dfs (5)
      • 类似节点 4 的计算
      • 左链长 = 0,右链长 = 0
      • 更新 ans = max (0, 0+0) = 0
      • 返回 0(节点 5 的最长链长)
    • 回到节点 2 的计算
      • 左链长 = dfs (4) + 1 = 0 + 1 = 1
      • 右链长 = dfs (5) + 1 = 0 + 1 = 1
      • 更新 ans = max (0, 1+1) = 2
      • 返回 max (1, 1) = 1(节点 2 的最长链长)
  • 计算右子树(节点 3):调用 dfs (3)

    • 节点 3 的左右子树都是 nullptr
    • 左链长 = dfs (nullptr) + 1 = 0
    • 右链长 = dfs (nullptr) + 1 = 0
    • 更新 ans = max (2, 0+0) = 2
    • 返回 0(节点 3 的最长链长)
  • 回到根节点 1 的计算

    • 左链长 = dfs (2) + 1 = 1 + 1 = 2
    • 右链长 = dfs (3) + 1 = 0 + 1 = 1
    • 更新 ans = max (2, 2+1) = 3
    • 返回 max (2, 1) = 2(根节点 1 的最长链长)
  • 最终结果

    • 函数返回 ans = 3,即这棵二叉树的直径
  • 关键逻辑总结:

  • 每个节点都会计算以自己为 "拐弯点" 的路径长度(左右链长之和)
  • 递归过程中不断更新全局最大直径
  • 函数返回的是当前节点能提供给父节点的最长链长(供上层节点计算使用)
  •  

    通过这种方式,代码能检查所有可能的拐弯点,最终找到整个树的最大直径。

三、广度优先搜索(BFS)与层序遍历

6. 二叉树的层序遍历(中等)

  • 核心逻辑:用队列实现广度优先搜索,逐层遍历节点,记录每一层的节点值。
    class Solution {
    public:
        vector<vector<int>> levelOrder(TreeNode* root) {
            if(root == nullptr){
                return {};
            }
            vector<vector<int>> ans;
            queue<TreeNode*> q;
            q.push(root);
            while(!q.empty()){
                vector<int> vals;//用来记录这一层
                for(int n = q.size();n--;){
                    //拿到队列第一个 并弹出
                    auto node = q.front();
                    vals.emplace_back(node->val);
                    q.pop();
                    //看左右子树
                    if(node->left){
                        q.push(node->left);
                    }
                    if(node->right){
                        q.push(node->right);
                    }
                }
                ans.emplace_back(vals);
            }
            return ans;
        }
    };
    

    7. 二叉树的右视图(中等)

  • 核心逻辑:层序遍历基础上,取每一层最后一个节点的值,即为右视图结果。
    class Solution {
    public:
        vector<int> rightSideView(TreeNode* root) {
            vector<int> result;
            if (root == nullptr) {
                return result;
            }
    
            // 使用队列进行层序遍历
            queue<TreeNode*> q;
            q.push(root);
    
            while (!q.empty()) {
                // 记录当前层的节点数量
                int levelSize = q.size();
    
                // 遍历当前层的所有节点
                for (int i = 0; i < levelSize; i++) {
                    TreeNode* node = q.front();
                    q.pop();
    
                    // 只保留每一层的最后一个节点
                    if (i == levelSize - 1) {
                        result.push_back(node->val);
                    }
    
                    // 将下一层的节点加入队列
                    if (node->left != nullptr) {
                        q.push(node->left);
                    }
                    if (node->right != nullptr) {
                        q.push(node->right);
                    }
                }
            }
    
            return result;
        }
    };
    
    写法二:一种逆向思维 先递归右子树 再递归左子树 
    class Solution {
    public:
        vector<int> rightSideView(TreeNode* root) {
            vector<int> ans;
            auto dfs = [&](this auto&& dfs, TreeNode* node, int depth) -> void {
                if (node == nullptr) {
                    return;
                }
                if (ans.size() == depth) {//这个深度首次遇到
                    ans.push_back(node->val);
                }
                dfs(node->right, depth + 1);//先递归右子树,保证首次遇到的一定是最右边的节点
                dfs(node->left, depth + 1);
            };
            dfs(root, 0);
            return ans;
        }
    };

    四、二叉搜索树(BST)专项

    8. 将有序数组转换为二叉搜索树(简单)

  • 核心逻辑:利用 BST “左小右大” 特性,取数组中间元素为根,递归构建左右子树(平衡 BST)。
    class Solution {
        TreeNode* dfs(vector<int>& nums,int left ,int right){
           if(left == right){
            return nullptr;
           }
           int m = left + (right - left) / 2;
           return new TreeNode(nums[m],dfs(nums,left,m),dfs(nums,m+1,right));
        }
    public:
        TreeNode* sortedArrayToBST(vector<int>& nums) {
            return dfs(nums,0,nums.size());
        }
    };

    9. 验证二叉搜索树(中等)

  • 核心逻辑:BST 的中序遍历是严格递增序列,或递归时传递当前子树的取值范围(min_val, max_val )。
    class Solution {
    private: long long pre = LLONG_MIN;
    
    public:
        bool isValidBST(TreeNode* root) {
            if(root == nullptr){
                return true;
            }
            if(!isValidBST(root->left)){
                return false;
            }
            if(root->val <= pre){
                return false;
            }
            pre = root->val;
            return isValidBST(root->right);
        }
    };

    10. 二叉搜索树中第 K 小的元素(中等)

  • 核心逻辑:BST 中序遍历是递增序列,遍历到第 k-1 个元素时返回结果(优化:递归时提前终止)。
  • 写法一:记录答案
    class Solution {
    public:
        int kthSmallest(TreeNode* root, int k) {
            int ans;
            auto dfs = [&](this auto&& dfs,TreeNode* node)->void{
                if(node == nullptr || k == 0){
                    return;
                }
                dfs(node->left);//左
                if(--k == 0){//中序遍历
                    ans = node->val;//根
                }
                dfs(node->right);//右
            };
            dfs(root);
            return ans;
        }
    };
    
    写法二:不记录答案 , 直接返回
    class Solution {
    public:
        int kthSmallest(TreeNode* root, int& k) {
            if(root == nullptr){
                return -1;//题目保证节点值非负 , 用 -1 表示没有找到
            }
            int left_res = kthSmallest(root->left,k);
            if(left_res != -1){//答案在左子树中
                return left_res;
            }
            if(--k == 0){
                return root->val;
            }
            return kthSmallest(root->right,k);//右子树会返回答案或者-1
        }
    };
    

  • - 递归边界:空节点返回-1(未找到)。

  • - 中序遍历:先递归左子树,若返回值非-1则直接返回(已找到答案)。

  • - 否则k减1,若k=0则返回当前节点值(找到答案)。

  • - 最后递归右子树,直接返回其结果(右子树找到则返回答案,否则返回-1)。

由于篇幅太长 , 五,六放到下一次再写

  • 五、二叉树结构转换与重构

  • 六、路径与公共祖先问题

  • 七、总结与学习建议

  • 递归思维是核心:二叉树天然适合递归,掌握 “分解子问题、终止条件、递归调用” 三步法,大部分题目可迎刃而解。
  • 遍历方式灵活用:DFS(前 / 中 / 后序)适合深度相关问题,BFS(层序)适合逐层操作;迭代实现能帮你理解递归本质。
  • 专项突破:BST 的特性(中序递增、左小右大)是解题关键;结构转换类题目需关注指针操作和遍历顺序。
  • 刻意练习 + 总结:刷完题目后,对比不同解法的时间 / 空间复杂度,思考 “为什么这样做”,形成自己的解题模板。
  • 掌握这些二叉树题目,不仅能应对面试高频考点,更能强化递归、分治等算法思维。收藏本文,按模块刷题,遇到卡壳的地方回头看思路,二叉树这关一定能过!

     

    (如果需要某道题的详细动画演示、复杂度分析拓展,或其他语言实现,欢迎在评论区留言~)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值