一、题目描述
给你一棵二叉树的根节点 root,翻转这棵二叉树,并返回其根节点。
翻转:交换每个节点的左子树和右子树。
示例
| 示例 | 输入 | 输出 |
|---|---|---|
| 示例1 | root = [4,2,7,1,3,6,9] |
[4,7,2,9,6,3,1] |
| 示例2 | root = [2,1,3] |
[2,3,1] |
| 示例3 | root = [] |
[] |
提示
- 树中节点数目范围在
[0, 100]内 -100 <= Node.val <= 100
二、解题思路
| 方法 | 核心思想 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 递归 | 前序遍历,交换左右子树 | O(n) | O(h),h为树高 |
核心公式:
翻转(root) = 交换左右子树 + 递归翻转左子树 + 递归翻转右子树
三、完整代码
cpp
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root; // 1. 空节点,直接返回
TreeNode* left = invertTree(root->left); // 2. 递归翻转左子树
TreeNode* right = invertTree(root->right); // 3. 递归翻转右子树
root->left = right; // 4. 交换左右子树
root->right = left;
return root; // 5. 返回根节点
}
};
四、算法流程图
以 root = [4,2,7,1,3,6,9] 为例:
翻转前: 翻转后:
4 4
/ \ / \
2 7 ────→ 7 2
/\ /\ /\ /\
1 3 6 9 9 6 3 1
递归展开过程(自底向上):
invertTree(4)
│
├── invertTree(2) ──→ 返回2,左右交换后2不变
│
├── invertTree(7) ──→ 返回7,左右交换后7不变
│
└── 交换4的左右子树:4->left=7, 4->right=2
详细展开:
invertTree(4)
├── invertTree(2)
│ ├── invertTree(1) ──→ return 1
│ ├── invertTree(3) ──→ return 3
│ └── 交换2的左右:2->left=3, 2->right=1 → return 2
│
├── invertTree(7)
│ ├── invertTree(6) ──→ return 6
│ ├── invertTree(9) ──→ return 9
│ └── 交换7的左右:7->left=9, 7->right=6 → return 7
│
└── 交换4的左右:4->left=7, 4->right=2 → return 4
五、逐行解析
cpp
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
// ─────────────────────────────────────────
// 第1步:递归终止条件
// 空节点翻转后还是空,直接返回
// ─────────────────────────────────────────
if (root == NULL) return root;
// ─────────────────────────────────────────
// 第2步:递归翻转左子树
// 子树翻转后赋值给left
// 后续交换时会放到right的位置
// ─────────────────────────────────────────
TreeNode* left = invertTree(root->left);
// ─────────────────────────────────────────
// 第3步:递归翻转右子树
// 子树翻转后赋值给right
// 后续交换时会放到left的位置
// ─────────────────────────────────────────
TreeNode* right = invertTree(root->right);
// ─────────────────────────────────────────
// 第4步:交换左右子树
// 原本的左子树放到右边
// 原本的右子树放到左边
// ─────────────────────────────────────────
root->left = right; // 左指针指向翻转后的右子树
root->right = left; // 右指针指向翻转后的左子树
// ─────────────────────────────────────────
// 第5步:返回翻转后的根节点
// 供上层节点继续处理
// ─────────────────────────────────────────
return root;
}
};
六、复杂度分析
时间复杂度
| 分析 | 复杂度 |
|---|---|
| 每个节点访问一次 | O(n) |
推导:n 个节点,每个节点处理一次(递归调用+交换)。
空间复杂度
| 分析 | 复杂度 |
|---|---|
| 函数调用栈,最大深度为树高h | O(h) |
推导:
- 最坏情况(链表形状):h = n,复杂度 O(n)
- 平衡树情况:h = log n,复杂度 O(log n)
七、面试追问 FAQ
| 问题 | 回答 |
|---|---|
| 为什么用前序遍历? | 前序遍历先处理根,再处理子树,适合这题自顶向下的交换 |
| 可以用中序或后序吗? | 中序不行(会翻两次),后序可以 |
| 递归和迭代哪个更好? | 递归代码简洁,迭代需要用栈模拟 |
| 翻转的本质是什么? | 把每个节点的左右子树指针交换 |
| 如何用迭代实现? | 层序遍历,访问每个节点时交换其左右子树 |
八、相关题目
| 题目 | 难度 | 关键点 |
|---|---|---|
| 226. 翻转二叉树 | 简单 | 本题 |
| 104. 二叉树的最大深度 | 简单 | 递归遍历 |
| 100. 相同的树 | 简单 | 递归比较 |
| 101. 对称二叉树 | 简单 | 翻转比较 |
九、总结
| 对比项 | 说明 |
|---|---|
| 代码行数 | 核心5行 |
| 时间复杂度 | O(n) |
| 空间复杂度 | O(h) |
| 递归顺序 | 前序遍历(根-左-右) |
| 核心操作 | 交换左右子树指针 |
核心思想:把大树拆成小树,翻转左右子树,再交换回来。