一、开篇引言
二叉树作为数据结构中的核心内容,不仅是大厂面试高频考点,更是理解复杂树结构、递归思维的基石。无论是刷题入门,还是冲刺算法进阶,二叉树相关题目都是绕不开的 “必修课”。本文整理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 的最长链长)
- 计算节点 2 的左子树(节点 4):调用 dfs (4)
-
计算右子树(节点 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 的特性(中序递增、左小右大)是解题关键;结构转换类题目需关注指针操作和遍历顺序。
- 刻意练习 + 总结:刷完题目后,对比不同解法的时间 / 空间复杂度,思考 “为什么这样做”,形成自己的解题模板。
-
掌握这些二叉树题目,不仅能应对面试高频考点,更能强化递归、分治等算法思维。收藏本文,按模块刷题,遇到卡壳的地方回头看思路,二叉树这关一定能过!
(如果需要某道题的详细动画演示、复杂度分析拓展,或其他语言实现,欢迎在评论区留言~)