题目描述
思路解析
一句话答案: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 一行同时兜住这两种情况。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住「换一对孩子,再钻进去换」——下面每个节点都在套它。
这是原始的树:根是 4,左子树根 2、右子树根 7 明显不对称。下面用前序递归,逐个节点交换左右孩子。
访问节点 4:它的左孩子是 2、右孩子是 7。把这两棵子树(紫色,连同它们各自的后代)整块对调。
交换完成:原来在左边的 2 这一支挪到了右边,原来在右边的 7 这一支挪到了左边。节点 4 处理好(变绿),接着递归进它的左右孩子。
访问节点 7:它的左孩子是 6、右孩子是 9。把这两棵子树(紫色,连同它们各自的后代)整块对调。
交换完成:原来在左边的 6 这一支挪到了右边,原来在右边的 9 这一支挪到了左边。节点 7 处理好(变绿),接着递归进它的左右孩子。
访问节点 9:它的左孩子是 12、右孩子是 15。把这两棵子树(紫色,连同它们各自的后代)整块对调。
交换完成:原来在左边的 12 这一支挪到了右边,原来在右边的 15 这一支挪到了左边。节点 9 处理好(变绿),接着递归进它的左右孩子。
节点 15 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
节点 12 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
节点 6 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
null 是空位/空节点,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
null 是空位/空节点,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
访问节点 2:它的左孩子是 1、右孩子是 3。把这两棵子树(紫色,连同它们各自的后代)整块对调。
交换完成:原来在左边的 1 这一支挪到了右边,原来在右边的 3 这一支挪到了左边。节点 2 处理好(变绿),接着递归进它的左右孩子。
访问节点 3:它的左孩子是 11、右孩子是 0。把这两棵子树(紫色,连同它们各自的后代)整块对调。
交换完成:原来在左边的 11 这一支挪到了右边,原来在右边的 0 这一支挪到了左边。节点 3 处理好(变绿),接着递归进它的左右孩子。
访问节点 1:它的左孩子是 8、右孩子是 5。把这两棵子树(紫色,连同它们各自的后代)整块对调。
交换完成:原来在左边的 8 这一支挪到了右边,原来在右边的 5 这一支挪到了左边。节点 1 处理好(变绿),接着递归进它的左右孩子。
节点 5 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
节点 8 是叶子,没有孩子可交换,递归直接返回。它就地处理好(变绿)。
所有节点都交换过左右孩子,整棵树相对原树完成了左右镜像翻转。返回根节点 4。
空 / 单节点 / 单边孩子三种边界,递归与交换天然兜住。
两个高频追问:递归只是其中一种遍历载体,换成迭代/任意遍历都成立。
参考代码
def invertTree(root): if not root: return None # ① 空节点直接返回 root.left, root.right = \ root.right, root.left # ② 交换左右孩子 invertTree(root.left) # ③ 递归翻左子树 invertTree(root.right) # 递归翻右子树 return root复杂度
- 时间:O(n),每个节点恰好访问并交换一次
- 空间:O(h),递归栈深度 = 树高 h,最坏 O(n)(退化成链),平衡时 O(log n)
易错点
面试追问把动画讲成自己的话
追问能不能用迭代(非递归)实现?
追问前序、后序、层序哪种遍历都行吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
二叉树的最大深度
LeetCode 104 · 简单 · 沿着 树 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题