翻转二叉树:先交换左右,再递归
每个节点只做一件事:左右孩子互换。换完把新的左、右再交给同一套函数,整棵树就镜像过来。
空节点返回空。否则交换当前节点的 Left 与 Right,再递归 invert(Left)、invert(Right)。先换后递归和先递归后换结果相同,因为交换的是指针,子树内部的翻转彼此独立。时间 O(n),空间 O(h)。
给你一棵二叉树的根,翻转它并返回根。翻转的定义是:每个节点的左右子树对调,对调后的左右子树自己也要再翻转。
主例层序 [4, 2, 7, 1, 3, 6, 9]。根 4 左边是 2(下挂 1、3),右边是 7(下挂 6、9)。翻完根左边变成 7(下挂 9、6),右边变成 2(下挂 3、1),层序 [4, 7, 2, 9, 6, 3, 1]。
本文要回答:为什么「每个节点交换一次左右指针」就够了,以及主例里三次交换分别发生在哪个节点上。
每个节点:左右对调
整棵树的镜像,拆到一个节点上就是:它的左孩子和右孩子互换位置。左子树、右子树各自还是一棵树,对它们做同一件事。缺的不是新的遍历顺序,而是承认这件事是局部的:每个节点只碰自己的两条边,不改自己的值。
代码里先交换当前节点的 Left 和 Right,再对交换之后的左右孩子递归。Go 的平行赋值不需要临时变量。先递归再交换也可以:子树先翻完,再把两条边对调,最后形状一样。两种顺序都成立,因为交换的是指向子树的指针,子树内部怎么翻和指针谁左谁右是两件独立的事。
用手走主例。根 4:左 2 和右 7 互换,树变成 4 左 7 右 2。演示场景第一步停在这里。进入现在的左孩子 7:它原来的左 6、右 9 互换,变成左 9 右 6。再进入现在的右孩子 2:原来的左 1、右 3 互换,变成左 3 右 1。叶子 9、6、3、1 的左右都是空,交换两个空指针,什么也没变。
层序读下来是 [4, 7, 2, 9, 6, 3, 1]。原来在 2 左边的 1 现在到了整棵树的最右下;原来在 7 右边的 9 现在到了最左下。每个节点进出一次,没有节点被漏换,也没有节点被换两次。
空树直接返回空。只有一个节点,左右都空,交换后还是它自己。一层只有左孩子或只有右孩子时,交换会把那唯一的孩子甩到对面,再递归进去继续翻。BFS 层序队列也能做:弹出一个节点就交换它的左右,再把非空孩子入队,效果相同。
局部叠成全体整棵树翻转 = 每个节点各做一次左右指针交换。递归或队列只负责把这次局部操作铺到每一个节点上,不负责发明第二种交换规则。
Go:递归交换
func invertTree(root *TreeNode) *TreeNode {if root == nil { return nil }root.Left, root.Right = root.Right, root.LeftinvertTree(root.Left)invertTree(root.Right)return root}
1空节点是递归出口,也避免对空指针写 Left、Right。
2平行赋值交换两条边。主例根 4 在这一行把 2 和 7 对调。
3交换之后再递归:先走现在的左(原来的右 7),再走现在的右(原来的左 2)。
4返回的仍是原来的根指针,树没有被换成新节点。
总结
每个节点交换左右孩子,再递归。主例 [4,2,7,1,3,6,9] 翻成 [4,7,2,9,6,3,1]。
- 先换后递归和先递归后换,形状相同。
- 空对空的交换是空操作,叶子不必特判。
- 层序队列同样是「弹出就交换,非空孩子入队」。