📌 题目链接:226. 翻转二叉树 - 力扣(LeetCode)
🔍 难度:简单 | 🏷️ 标签:树、深度优先搜索(DFS)、递归
⏱️ 时间复杂度:O(N)
💾 空间复杂度:O(H),H 为树的高度(最坏 O(N),平均 O(log N))
🧠 题目分析
本题要求我们翻转一棵二叉树,即对于每个节点,将其左右子树交换位置。这听起来简单,但背后蕴含了经典的树遍历 + 递归结构思想。
💡 面试高频点:
- “翻转二叉树” 是 Google 曾经的面试题(Max Howell 的梗:“Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f*** off.”)
- 考察你对递归终止条件、子问题分解、树结构操作的理解。
输入是一棵二叉树的根节点 root,输出是翻转后的根节点。注意:
- 空树直接返回
nullptr - 翻转是原地操作(虽然题目没强制,但通常期望如此)
- 不需要新建节点,只需调整指针方向
🔁 核心算法及代码讲解:递归 + 深度优先搜索(DFS)
✅ 核心思想
采用后序遍历(Post-order Traversal) 的递归策略:
- 先递归翻转左子树
- 再递归翻转右子树
- 最后将当前节点的左右子树指针交换
📌 为什么是后序?
因为我们希望先处理子树,再处理当前节点。只有当左右子树已经翻转完毕,我们才能安全地交换它们。这正是后序遍历的典型应用场景。
🧩 递归三要素
| 要素 | 说明 |
|---|---|
| 递归函数定义 | invertTree(root) 返回以 root 为根的翻转后二叉树的根 |
| 终止条件 | root == nullptr,空节点无需翻转,直接返回 |
| 递归关系 | 翻转左子树 → 翻转右子树 → 交换左右指针 |
💻 C++ 核心代码(带详细行注释)
TreeNode* invertHere(TreeNode* root) {
// 终止条件:空节点,直接返回
if (root == nullptr) {
return nullptr;
}
// 递归翻转左子树,得到翻转后的左子树根
TreeNode* left = invertHere(root->left);
// 递归翻转右子树,得到翻转后的右子树根
TreeNode* right = invertHere(root->right);
// 交换:原左子树变成右子树,原右子树变成左子树
root->left = right; // 注意:这里赋值的是已经翻转好的子树
root->right = left;
// 返回当前已翻转的子树根
return root;
}
⚠️ 注意:有些初学者会写成先交换再递归,那是前序遍历,也能正确翻转!但逻辑上不如后序清晰。两种方式都可行,但后序更符合“子问题先解决”的直觉。
🧭 解题思路(分步拆解)
-
理解“翻转”的含义
对于任意节点,其左孩子变为右孩子,右孩子变为左孩子。整棵树镜像对称。 -
选择遍历方式
-
前序(根→左→右):先交换当前节点的左右子树,再递归处理子树
-
后序(左→右→根):先递归处理子树,再交换当前节点的左右子树
✅ 两者均可!但后序更自然,因为我们在“回溯”时完成翻转。
-
-
处理边界情况
- 输入为空树(
root == nullptr)→ 直接返回nullptr - 只有根节点 → 交换左右(均为 null),结果不变
- 输入为空树(
-
实现递归函数
按照上述三要素编写,确保每层递归只做一次交换,且不遗漏任何节点。 -
验证示例
示例1:[4,2,7,1,3,6,9]- 翻转后应为
[4,7,2,9,6,3,1] - 手动画图可验证:4 的左右子(2,7)交换 → 2 的子(1,3)交换 → 7 的子(6,9)交换
- 翻转后应为
📊 算法分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(N) —— 每个节点访问一次 |
| 空间复杂度 | O(H) —— 递归栈深度等于树高 H • 平均情况(平衡树):O(log N) • 最坏情况(链状树):O(N) |
| 是否原地操作 | ✅ 是,仅修改指针,未创建新节点 |
| 可迭代实现吗? | ✅ 可用栈模拟 DFS(前序或后序),但递归更简洁 |
💬 面试加分项:
- 主动提到“也可以用 BFS(层序遍历)+ 队列实现”
- 比较递归 vs 迭代的优劣:递归代码简洁但有栈溢出风险;迭代更安全但代码稍长
- 强调“翻转是镜像操作,不是反转值”
💻 完整代码
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) {}
};
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) {
return nullptr;
}
TreeNode* left = invertTree(root->left);
TreeNode* right = invertTree(root->right);
root->left = right;
root->right = left;
return root;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 构建示例1: [4,2,7,1,3,6,9]
TreeNode* root = new TreeNode(4);
root->left = new TreeNode(2);
root->right = new TreeNode(7);
root->left->left = new TreeNode(1);
root->left->right = new TreeNode(3);
root->right->left = new TreeNode(6);
root->right->right = new TreeNode(9);
Solution sol;
TreeNode* inverted = sol.invertTree(root);
// 简单验证:中序遍历应为 9,7,6,4,3,2,1(非标准,仅示意)
// 实际建议用层序打印验证,此处省略
return 0;
}
JavaScript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if (root === null) {
return null;
}
const left = invertTree(root.left);
const right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
};
// 测试用例
// 构建树 [4,2,7,1,3,6,9]
const root = {
val: 4,
left: {
val: 2,
left: { val: 1, left: null, right: null },
right: { val: 3, left: null, right: null }
},
right: {
val: 7,
left: { val: 6, left: null, right: null },
right: { val: 9, left: null, right: null }
}
};
console.log(invertTree(root));
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!