翻转二叉树 图解题解
翻转整棵二叉树听着很大——实际上每个节点只做一件小事。
给一棵族谱照镜子:每个人只需要把自己的左右两个孩子互换,然后让两边各自继续照——同一个动作递归铺满全树,整棵族谱就镜像了。不用全局规划,也不用盯住所有层,每个节点各自管好自己这一步,空节点直接返回,整棵树自然就翻了。
这道题到底在问什么
- 输入
- 前序 [4,2,1,3,7,6,9,…]
- 输出
- 每个节点左右孩子互换后的镜像树
最优解:为什么这么做
一句话答案:LeetCode 226 翻转二叉树的标准解是递归:对每个节点交换它的 left、right 两个孩子指针,再分别递归翻转左右子树,整棵树就完成了镜像。每个节点恰好被访问并交换一次,时间 O(n);递归栈深等于树高,空间 O(h)。
翻转二叉树到底要翻什么
题目要求把一棵二叉树变成它的镜像:每个节点的左子树和右子树互换位置,返回翻转后的根。注意「每个节点」三个字——不只是根节点换一次,树里任何一层的任何节点,它的左右孩子都要对调。只在根上换一次,得到的树深层结构仍是原样,不是镜像。
为什么翻转二叉树天然适合递归
镜像有一个漂亮的自相似结构:整棵树的镜像 = 根不动,把原来的右子树的镜像挂到左边,把原来的左子树的镜像挂到右边。也就是说,「翻转一棵树」这个大问题,恰好等于「交换根的两个孩子」加上两个一模一样但更小的问题——翻转左子树、翻转右子树。问题的定义本身就是递归式的,代码照着定义抄下来就是答案。
于是函数只有三件事:空节点直接返回 None(递归出口);交换 root.left 和 root.right;再对新的左右孩子各调一次自己。不需要任何额外数据结构,也不需要记录状态。
交换的是指针,不是节点里的值
一个常见的错误是只交换左右孩子节点里存的数值。这在孩子是叶子时碰巧看不出问题,可一旦孩子下面还挂着后代,只换值等于把两棵子树的「门牌号」换了、里面的房间没搬——孩子各自的子孙还留在原来那一侧,结构就错了。正确做法是交换 left、right 两个指针:一次赋值就把整棵子树连同它所有后代整体搬到另一边,后代内部的翻转交给递归去完成。
先交换再递归,还是先递归再交换
两种顺序结果完全一样。原因是「交换某个节点的左右孩子」这个操作只动这个节点自己的两个指针,跟别的节点互不干扰;只要保证树里每个节点都被交换恰好一次,先处理谁都行。所以前序、后序甚至层序遍历都能做这道题——用队列或栈遍历,每弹出一个节点就交换它的孩子再把孩子入队,就是等价的迭代写法。真正不能省的是两件事都要做:只递归不交换,树纹丝不动;只交换根不递归,深层没翻。
复杂度怎么算,哪里容易翻车
时间 O(n):n 个节点每个恰好访问一次,每次只做常数次指针操作。空间 O(h):递归栈的深度等于树高 h,平衡树是 O(log n),退化成链的最坏情况是 O(n)。
最容易翻车的点是漏写空判断:递归走到叶子的孩子时传进来的是 None,不先返回就会对空对象取 left 而崩溃。另外空树本身也是合法输入,if not root: return None 一行同时兜住这两种情况。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记住「换一对孩子,再钻进去换」——下面每个节点都在套它。
- 4这是原始的树:根是 4,左子树根 2、右子树根 7 明显不对称。下面用前序递归,逐个节点交换左右孩子。
- 5访问节点 4:它的左孩子是 2、右孩子是 7。把这两棵子树(紫色,连同它们各自的后代)整块对调。
- 6交换完成:原来在左边的 2 这一支挪到了右边,原来在右边的 7 这一支挪到了左边。节点 4 处理好(变绿),接着递归进它的左右孩子。
- 7访问节点 7:它的左孩子是 6、右孩子是 9。把这两棵子树(紫色,连同它们各自的后代)整块对调。
- 8交换完成:原来在左边的 6 这一支挪到了右边,原来在右边的 9 这一支挪到了左边。节点 7 处理好(变绿),接着递归进它的左右孩子。
- 9访问节点 9:它的左孩子是 12、右孩子是 15。把这两棵子树(紫色,连同它们各自的后代)整块对调。
- 10交换完成:原来在左边的 12 这一支挪到了右边,原来在右边的 15 这一支挪到了左边。节点 9 处理好(变绿),接着递归进它的左右孩子。
- 11节点 15 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
- 12节点 12 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
- 13节点 6 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
- 14null 是空位/空节点,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
- 15null 是空位/空节点,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
- 16访问节点 2:它的左孩子是 1、右孩子是 3。把这两棵子树(紫色,连同它们各自的后代)整块对调。
- 17交换完成:原来在左边的 1 这一支挪到了右边,原来在右边的 3 这一支挪到了左边。节点 2 处理好(变绿),接着递归进它的左右孩子。
- 18访问节点 3:它的左孩子是 11、右孩子是 0。把这两棵子树(紫色,连同它们各自的后代)整块对调。
- 19交换完成:原来在左边的 11 这一支挪到了右边,原来在右边的 0 这一支挪到了左边。节点 3 处理好(变绿),接着递归进它的左右孩子。
- 20访问节点 1:它的左孩子是 8、右孩子是 5。把这两棵子树(紫色,连同它们各自的后代)整块对调。
- 21交换完成:原来在左边的 8 这一支挪到了右边,原来在右边的 5 这一支挪到了左边。节点 1 处理好(变绿),接着递归进它的左右孩子。
- 22节点 5 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
- 23节点 8 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
- 24所有节点都交换过左右孩子,整棵树相对原树完成了左右镜像翻转。返回根节点 4。
⚠️ 容易写错的地方
✗ 错:先递归再交换 / 漏掉交换
✓ 对:交换当前左右孩子 + 递归左右,两者都要
只递归不交换则树没变;先后顺序对结果无影响但都不能省
✗ 错:交换时只换了节点值没换整棵子树
✓ 对:交换的是 left/right 指针(整棵子树)
只换根值会丢掉孩子的后代结构,结果错
✗ 错:忘记 root 为空的判断
✓ 对:if not root: return
空树/递归到 null 时必须直接返回,否则空指针
完整代码(Python / C++ / Java)
Python
def invertTree(root):
if not root:
return None # ① 空节点直接返回
root.left, root.right = \
root.right, root.left # ② 交换左右孩子
invertTree(root.left) # ③ 递归翻左子树
invertTree(root.right) # 递归翻右子树
return rootC++
TreeNode* invertTree(TreeNode* root){
if(!root) return nullptr;
swap(root->left, root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}Java
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode tmp = root.left;
root.left = root.right; // 交换
root.right = tmp;
invertTree(root.left); // 递归左
invertTree(root.right); // 递归右
return root;
}复杂度
时间
O(n)
每个节点恰好访问并交换一次
空间
O(h)
递归栈深度 = 树高 h,最坏 O(n)(退化成链),平衡时 O(log n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 翻转二叉树 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能用迭代(非递归)实现?+
能。用队列或栈做层序/前序遍历,每弹出一个节点就交换它的左右孩子,再把孩子入队/入栈。逻辑和递归完全一致。
前序、后序、层序哪种遍历都行吗?+
都行。交换某节点左右孩子这个操作互不依赖、对遍历顺序不敏感,只要每个节点都被访问到一次即可。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 翻转二叉树 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。