合并二叉树:对着的位置相加,空的那边整棵接上
两棵树同一位置一起走。两边都有就新开节点写两值之和;有一边是空,把非空的那棵子树直接挂上去。
递归合并:merge(n1, n2):若 n1 为空返回 n2,n2 为空返回 n1;否则新节点值为 n1.val + n2.val,左右孩子递归合并。重叠节点相加、空侧透传。时间 O(min(n1,n2)),空间 O(min(h1,h2))。
给定两棵二叉树,把它们按位置叠在一起:同一位置都有节点,新节点的值是两者之和;只有一边有节点,就把那一边的节点(连同它下面整棵子树)接到结果上;两边都空则空。
主例 root1 = [1, 3, 2, 5],root2 = [2, 1, 3, null, 4](完整题面右侧 3 下还有 7)。根 1+2=3,左 3+1=4,右 2+3=5;A 的 5 对上 B 的空,原样留下;B 的 4 对上 A 的空,接上。结果 [3, 4, 5, 5, 4, null, 7]。演示先标根,再标左 3+1=4,最后给出整棵。
分别遍历再按层对下标相加,空位对不齐。本文要回答:为什么「一边空就返回另一边」同时处理了值和形状,以及主例 5 和 4 为什么不用再往下假造空节点去加。
缺口是对齐的一对节点,不是两份层序数组
第一直觉把两棵树层序铺开,下标相同的格子相加。主例 A 的层序是 1,3,2,5,B 是 2,1,3,空,4,下标 3 对上的是 5 和空,还算整齐;B 右侧若还有 7,层序空位一错,对下标就偏。缺的是按指针对齐:当前只处理「这两棵子树的根」这一对。
merge(n1, n2) 返回合并后的根。n1 空,结果就是 n2 整棵——B 多出来的形状原样接上,不必再遍历。n2 空,返回 n1。两边都在:新建节点,值是 n1.Val+n2.Val,左孩子 merge(n1.Left, n2.Left),右孩子 merge(n1.Right, n2.Right)。空对空会在下一层被两边的 nil 直接返回 nil。
「接上」是指针级的:返回的就是那棵已有子树,不拷贝。题目允许改原树时,也可以把和写进 n1 再接孩子,语义一样。教学上新建节点更干净,合同一眼能看完。
用手走主例。一对根 1 与 2,都非空,新根值 3,演示第一帧。左一对 3 与 1,新值 4,演示第二帧。这一对的左孩子是 5 与 nil,命中「B 空,返回 A 的 5」;右孩子是 nil 与 4,命中「A 空,返回 B 的 4」。右一对 2 与 3,新值 5;3 的右孩子 7 对上 A 的空,整棵 7 接上。终帧 [3,4,5,5,4,null,7]。
一棵空树与另一棵合并,答案就是那棵非空树,一次返回,零次相加。两棵都空,答案空。时间由重叠部分决定:只有两边都非空才继续往下,先走完的那一侧把剩余形状一次性挂上,所以是 O(min(n1,n2)),不是两棵尺寸相加。
空则接上nil 不是「加零再往下走」,是「这一侧没有形状,把另一侧整棵挂上来」。主例的 5 和 4 都走透传,不会被加出 5+0 的假节点再去递归空孩子。
Go:递归合并
func mergeTrees(n1, n2 *TreeNode) *TreeNode {if n1 == nil { return n2 }if n2 == nil { return n1 }return &TreeNode{Val: n1.Val + n2.Val,Left: mergeTrees(n1.Left, n2.Left),Right: mergeTrees(n1.Right, n2.Right),}}
1先处理空。主例 5 对 nil 走第二句,整棵 5 接上,不再相加。
2两边都在才新建并相加。根是 1+2=3,左是 3+1=4,与结果数组一致。
3左右各自配对递归。一侧多出来的子孙在某一层命中 nil 后整段透传。
总结
对着加,空的接上。主例根 1+2=3,左 3+1=4,结果含 5 与 4 两处透传。
- 不要按层序下标硬加,空位会对歪。一对指针才是对齐方式。
- nil 返回另一侧整棵,不是加 0 继续走。
- 只在重叠节点上深入,时间看较短的那棵。