题目描述
翻转(镜像)整棵二叉树:让每个节点的左右孩子互换,返回根。
思路解析
新手常卡在「整棵树那么多节点,到底先动谁、动完会不会影响别处」。如果把它当成一道「全局重排」的题,要同时盯住所有层、所有位置,脑子立刻不够用。
转折:翻转不需要全局视角。到任意节点,只把它的左、右两个孩子互换,再分别递归翻转左右子树,整棵树就自动镜像了。为什么对?因为「镜像」对每个节点的要求都一样——左右对调,递归正好把同一个动作铺满全树。空节点没孩子可换,直接返回。
原树 · 从根 4 出发:原树长这样:4 的左孩子是 2(带 1,3)、右孩子是 7(带 6,9)。递归从根 4 进入,第一件事就是交换它的左右孩子。
根 4 · 交换左右孩子:把 4 的左孩子(2)和右孩子(7)对调。注意:交换的是整棵子树,不是只换个数字——原来的右子树(7,6,9)整块挪到左边、左子树(2,1,3)整块挪到右边。
递归进左子树 · 现在是 7:根处理完,递归进它现在的左孩子 7(刚从右边搬过来)。对 7 做同一件事:交换它的两个孩子 6 和 9。
节点 7 · 交换 6 ↔ 9:7 的左孩子(6)和右孩子(9)对调 → 变成 (9, 6)。这俩都是叶子,整块搬就是搬一个节点。换完,继续往 7 的孩子递归。
递归进 9 · 空孩子直接返回:进入 9。它的左右孩子都是空,没什么可交换的——空节点是递归的底,直接返回(这就是负例:不是每次递归都真的交换)。它的兄弟 6 同理,也是空孩子直接返回。
左子树翻完 · 回到根 · 递归进右:左子树(7,9,6)全部翻完(标灰)。回到根 4,再递归进它现在的右孩子 2。对 2 做同一件事:交换它的孩子 1 和 3。
节点 2 · 交换 1 ↔ 3:2 的左孩子(1)和右孩子(3)对调 → 变成 (3, 1)。再递归进 3、进 1——都是空孩子的叶子,直接返回,无操作。
完成 · 整棵镜像:每个节点都被交换过一次,整棵树成了原树的镜像:4 →(7,2),7→(9,6),2→(3,1)。和题目给的「翻转后」完全一致。
很多树题的本质都是「对每个节点都做某个简单操作」,用递归自动覆盖全树,你只需想清楚单个节点该干嘛。翻转(换孩子)、给每个节点加值、收集每个节点,全是这个套路的换皮。
参考代码(Python)
1def invertTree(self, root):
2 if not root: return None # 空节点直接返回(递归的底)
3 root.left, root.right = root.right, root.left # 交换左右孩子(一行搞定)
4 self.invertTree(root.left) # 递归翻左
5 self.invertTree(root.right) # 递归翻右
6 return root复杂度分析
- 时间复杂度:O(n) —— 每个节点恰好被访问、交换一次
- 空间复杂度:O(h) —— 递归栈深度等于树高 h,最坏退化成链表时为 O(n)
套路模板
记住骨架:空则返回、对当前节点做操作、递归左右。只换中间那行「操作」,就能做翻转、加权、剪枝等一大类「对全树统一处理」的题。
1def dfs(node):
2 if not node: return # 空则返回(递归的底)
3 操作(node) # 对当前节点做事(翻转题=交换左右孩子)
4 dfs(node.left) # 递归左
5 dfs(node.right) # 递归右易错点
- 错误写法:root.left = invert(root.right); root.right = invert(root.left) → 正确写法:先用元组同时交换 root.left, root.right = root.right, root.left,或先暂存(第一行已把 root.left 改成了翻好的右子树,第二行的 root.left 是新值,整棵树乱套——必须先暂存或用元组赋值一步换好)
- 错误写法:只交换节点里的值,不动孩子指针 → 正确写法:交换的是 left/right 两个子树指针(镜像要把整棵子树搬过去,只换根的值,子树结构还是原样,根本没翻)
- 错误写法:忘了空节点的判断 → 正确写法:开头 if not root: return None(递归一定会走到叶子的空孩子,不挡住 None 就会在取 .left 时崩)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。