

🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:
我们今天学习的是二叉树的相关的算法题。
摘要:
本文介绍了翻转二叉树的两种主要方法:递归法和迭代法(BFS)。递归法采用前序/后序遍历,通过交换每个节点的左右子树实现翻转,时间复杂度O(n),空间复杂度O(h)。迭代法使用队列进行层序遍历,同样通过交换左右节点实现翻转,适合避免递归栈溢出。特别指出中序遍历的陷阱:若按常规写法会导致部分子树被翻转两次,需调整处理顺序。文章通过具体示例演示了各方法的执行过程,对比了不同遍历方式的适用场景,最终推荐前序遍历递归法作为最优解,因其代码简洁且符合直觉。
题目背景:226.翻转二叉树
给你一棵二叉树的根节点
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 = [] 输出:[]
题目解析:
我们之前介绍的都是各种方式遍历二叉树,这次要翻转了,感觉还是有点懵
这得怎么翻转呢
如果要从整个树来看,翻转还真的挺复杂,整个树以中间分割线进行翻转,如图:

可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
关键在于遍历顺序,前中后序应该选哪一种遍历顺序。遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次,建议拿纸画一画,就理解了
这里我们用两种方式分别解决:
递归法
对于二叉树的递归法的前中后序遍历,已经在二叉树:前中后序递归遍历详细讲解了。
我们下文以前序遍历为例,通过动画来看一下翻转的过程:
我们来看一下递归三部曲:
- 确定递归函数的参数和返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。
返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为
TreeNode*。
TreeNode* invertTree(TreeNode* root)
- 确定终止条件
当前节点为空的时候,就返回
if (root == NULL) return root;
- 确定单层递归的逻辑
因为是前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。
swap(root->left, root->right); invertTree(root->left); invertTree(root->right);因此逻辑还是很简单的,递归的调用而已
操作 作用对象 结果 交换左右节点 当前节点的两个孩子指针 左右子树对调 递归翻转子树 每个子树内部的所有节点 子树内部的左右结构也对调
广度优先遍历:
当前节点 = 4 交换前: 4 / \ 2 7 交换后: 4 / \ 7 2 然后将左右子节点(7和2)加入队列: 队列:[7, 2]当前树状态:
4 / \ 7 2 / \ / \ 6 9 1 3
第2轮循环(处理节点7)
当前节点 = 7 交换前: 7 / \ 6 9 交换后: 7 / \ 9 6 将子节点加入队列: 队列:[2, 9, 6]树状态:
4 / \ 7 2 / \ / \ 9 6 1 3
第3轮循环(处理节点2)
当前节点 = 2 交换前: 2 / \ 1 3 交换后: 2 / \ 3 1 将子节点加入队列: 队列:[9, 6, 3, 1]树状态:
4 / \ 7 2 / \ / \ 9 6 3 1
第4-7轮循环(处理叶子节点)
节点9:左右都是null,交换无变化,子节点不加入队列 节点6:左右都是null,交换无变化 节点3:左右都是null,交换无变化 节点1:左右都是null,交换无变化 最终队列为空,结束
两种方法对比
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 递归 | O(n) | O(h),h 为树高(最坏 O(n)) | 代码简洁,推荐面试用 |
| 迭代(BFS) | O(n) | O(n) | 避免递归栈溢出风险 |
补充中序遍历
很多人会这样写(这是错的):
java
public TreeNode invertTree(TreeNode root) { if (root == null) return null; invertTree(root.left); // 翻转左子树 // 交换左右孩子 TreeNode temp = root.left; root.left = root.right; root.right = temp; invertTree(root.right); // 翻转右子树 ← 这里有问题 return root; }为什么错
因为交换后,原来的右子树被移到了左边 ,但代码还在用
root.right去递归用具体例子演示:
4 / \ 2 5 / \ 1 3 ① invertTree(2的左边=1) → 无变化 ② 在节点2处:交换1和3 → 2的左右变成[3, 1] ③ invertTree(2的右边) → 此时2的右边是1,递归处理1(正常) ④ 返回到节点4:交换2和5 → 4的左右变成[5, 2] ⑤ invertTree(4的右边) → 此时4的右边是2,递归处理2 问题来了:节点2下面现在是[3, 1]还是[1, 3] 实际上节点2已经被处理过,它的子树是[3, 1](已翻转) 但invertTree(2)会再次翻转它,把它变回[1, 3] 结果:节点2被翻转了两次,等于没翻❌✅ 正确的中序遍历写法
只要调整递归顺序就能工作:
java
public TreeNode invertTree(TreeNode root) { if (root == null) return null; invertTree(root.left); // 翻转左子树 // 交换左右孩子 TreeNode temp = root.left; root.left = root.right; root.right = temp; // 关键:这里要递归新的左子树(它原来是右子树) invertTree(root.left); // ← 改这里! return root; }为什么这次对了
因为交换后,原来的右子树跑到了左边,所以继续处理左边就能把原来的右子树翻转完。
text
演示正确的流程: 4 / \ 2 5 / \ 1 3 1. invertTree(2): a) invertTree(1) → 无变化 b) 交换2的左右:[3, 1] c) invertTree(2的新左边=3) → 翻转3(无子节点) 2. 回到节点4: a) 交换4的左右:[5, 2](注意2下面是[3,1]) b) invertTree(4的新左边=5) → 翻转5(无子节点) 结果正确!✅
📊 四种遍历方式对比
| 遍历方式 | 是否可行 | 代码简洁度 | 注意事项 |
|---|---|---|---|
| 前序 | ✅ 完美 | 最简洁 | 最推荐,思路直接 |
| 后序 | ✅ 完美 | 简洁 | 也推荐,从下往上 |
| 中序 | ⚠️ 可行但不推荐 | 易错 | 交换后要处理左边,而非右边 |
| 层序 | ✅ 完美 | 稍长(需队列) | 避免递归深度过大 |
题目答案:
递归法:
java
class Solution {
public TreeNode invertTree(TreeNode root) {
// 递归终止条件:空节点直接返回
if (root == null) {
return null;
}
// 交换当前节点的左右子节点
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
// 递归翻转左右子树
invertTree(root.left);
invertTree(root.right);
// 返回翻转后的根节点
return root;
}
}
BFS法:
java
import java.util.LinkedList;
import java.util.Queue;
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode current = queue.poll();
// 交换左右孩子
TreeNode temp = current.left;
current.left = current.right;
current.right = temp;
// 将非空子节点加入队列
if (current.left != null) queue.offer(current.left);
if (current.right != null) queue.offer(current.right);
}
return root;
}
}


