题目描述
思路解析
一句话答案:LeetCode 617 合并二叉树的标准解法是同步递归:两棵树从根开始一起往下走,对应位置都有节点就把值相加,一边为空就直接把另一边整棵子树接过来,然后对两边的左孩子、两边的右孩子递归做同样的事。只有两树重叠的部分才真正递归,时间 O(min(m,n))、空间 O(min(h1,h2)) 递归栈。
合并规则到底是什么
给两棵二叉树 t1、t2,把它们按位置叠加成一棵树:某个位置两棵树都有节点,新值等于两值之和;只有一棵有节点,就原样采用那个节点连同它下面的整棵子树。结果树的形状是两棵树形状的并集。可以想象两张画着树的透明胶片叠在一起:重叠处数值相加,不重叠处露出谁就是谁。
为什么这道题天然适合同步递归
合并的定义本身就是递归的:合并两棵树,等于合并两个根,再分别合并两边的左子树、两边的右子树——子问题和原问题一模一样,只是规模更小。于是让两个指针 a、b 同步下行,每一步只处理一对对应位置的节点。相比先把两棵树各自遍历成序列再对齐相加,同步递归根本不用处理两树形状不一致的对齐难题:位置的对应关系由递归结构自动维护,a 的左孩子永远只和 b 的左孩子配对。
一边为空为什么可以整支搬走
递归的终止条件是这题的精髓:a 为空就直接返回 b,b 为空就直接返回 a。理由是一旦一边为空,它下面的所有位置也全是空,按规则这一整片区域的结果都等于另一棵树的原样——那就不必再逐个节点走进去确认,直接把整棵子树接上。这一步不只是写法上的简洁,它直接决定了复杂度:递归只深入两树都有节点的重叠区域,非重叠的部分一步接走、零成本。
原地复用还是新建节点
参考代码选择原地复用:把 b 的值累加进 a(a.val += b.val),左右孩子分别接上递归结果,最后返回 a,全程不新建节点,空间开销最小。如果场景要求不修改输入,也可以每步新建节点、值取两者之和,逻辑完全不变,多花一份结果树的空间。
最容易犯的错是把终止条件写成「一边为空就返回空」——这会把另一边还完好的整棵子树直接丢掉,合并出的树缺一大块。正确语义是「一边为空,答案就是另一边」。
复杂度为什么是 O(min(m,n))
真正的递归只发生在两树重叠的位置:设两棵树分别有 m、n 个节点,重叠位置至多 min(m,n) 个,每个位置做一次相加和两次递归调用,时间 O(min(m,n))。递归栈深等于重叠部分的高度,即 O(min(h1,h2)),最坏两棵树都退化成重合的链时为 O(n)。另一个细节:合并是逐位置的,值相加之后必须继续递归左右孩子,只把两个根相加、忘了往下走,孩子层的叠加根本没发生。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
两棵树像两张透明胶片叠在一起:同步往下走。一边为空是天然的终止条件——直接把另一边整支搬过来,不必再钻进空子树。下面逐个位置看这个叠加过程。
准备 · 结果树骨架:先看结果树的「骨架」:它的形状是两棵树形状的并集——只要任意一棵在某位置有节点,结果树这里就有。每个位置的值待定(占位 ·)。接下来从根出发,按前序(根→左→右)逐个位置算出合并值。
位置 层序#0 · 两边都有:走到层序位置 0:t1 这里是 1、t2 这里是 2,两棵树同一个位置都有节点。这时就把两个值相加:1+2 = 3,作为合并后这个位置的新值。
落子 层序#0 · 合并值 = 3:把合并值 3 落进结果树的层序位置 0(绿)。它前面的位置都已确定(蓝)。接着按前序继续递归它的左、右孩子。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#1 · 两边都有:走到层序位置 1:t1 这里是 3、t2 这里是 1,两棵树同一个位置都有节点。这时就把两个值相加:3+1 = 4,作为合并后这个位置的新值。
落子 层序#1 · 合并值 = 4:把合并值 4 落进结果树的层序位置 1(绿)。它前面的位置都已确定(蓝)。接着按前序继续递归它的左、右孩子。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#3 · 只有左树:走到层序位置 3:t1=5、t2=空,只有一棵树有节点。一边为空时不用算,直接把非空那一支整个搬过来当结果——这正是递归的终止边界,省掉了对空子树的继续递归。
落子 层序#3 · 合并值 = 5:把合并值 5 落进结果树的层序位置 3(绿)。它前面的位置都已确定(蓝)。这一支已整体接上,无需再深入。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#7 · 只有左树:走到层序位置 7:t1=8、t2=空,只有一棵树有节点。一边为空时不用算,直接把非空那一支整个搬过来当结果——这正是递归的终止边界,省掉了对空子树的继续递归。
落子 层序#7 · 合并值 = 8:把合并值 8 落进结果树的层序位置 7(绿)。它前面的位置都已确定(蓝)。这一支已整体接上,无需再深入。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#4 · 两边都有:走到层序位置 4:t1 这里是 7、t2 这里是 4,两棵树同一个位置都有节点。这时就把两个值相加:7+4 = 11,作为合并后这个位置的新值。
落子 层序#4 · 合并值 = 11:把合并值 11 落进结果树的层序位置 4(绿)。它前面的位置都已确定(蓝)。接着按前序继续递归它的左、右孩子。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#10 · 只有右树:走到层序位置 10:t1=空、t2=11,只有一棵树有节点。一边为空时不用算,直接把非空那一支整个搬过来当结果——这正是递归的终止边界,省掉了对空子树的继续递归。
落子 层序#10 · 合并值 = 11:把合并值 11 落进结果树的层序位置 10(绿)。它前面的位置都已确定(蓝)。这一支已整体接上,无需再深入。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#2 · 两边都有:走到层序位置 2:t1 这里是 2、t2 这里是 3,两棵树同一个位置都有节点。这时就把两个值相加:2+3 = 5,作为合并后这个位置的新值。
落子 层序#2 · 合并值 = 5:把合并值 5 落进结果树的层序位置 2(绿)。它前面的位置都已确定(蓝)。接着按前序继续递归它的左、右孩子。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#5 · 只有右树:走到层序位置 5:t1=空、t2=6,只有一棵树有节点。一边为空时不用算,直接把非空那一支整个搬过来当结果——这正是递归的终止边界,省掉了对空子树的继续递归。
落子 层序#5 · 合并值 = 6:把合并值 6 落进结果树的层序位置 5(绿)。它前面的位置都已确定(蓝)。这一支已整体接上,无需再深入。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
位置 层序#6 · 两边都有:走到层序位置 6:t1 这里是 9、t2 这里是 8,两棵树同一个位置都有节点。这时就把两个值相加:9+8 = 17,作为合并后这个位置的新值。
落子 层序#6 · 合并值 = 17:把合并值 17 落进结果树的层序位置 6(绿)。它前面的位置都已确定(蓝)。接着按前序继续递归它的左、右孩子。 同步递归的节奏就是:处理当前位置 → 递归左 → 递归右。
答案 · 合并结果 = [3,4,5,5,11,6,17,8,null,null,11]:所有位置都合并完了:层序读出来正好是 [3,4,5,5,11,6,17,8,null,null,11]。整道题原地复用 t1 的节点(值就地累加),空间不额外开新树。回头看每一格:都有就相加、只一边就搬过来——同步递归把两棵树干净地叠成了一棵。
参考代码
class Solution { public TreeNode mergeTrees(TreeNode a, TreeNode b) { if (a == null) return b; // 一边空 → 搬另一边整支 if (b == null) return a; a.val += b.val; // 都非空 → 对应位置相加 a.left = mergeTrees(a.left, b.left); // 递归合并左 a.right = mergeTrees(a.right, b.right); // 递归合并右 return a; }}复杂度
- 时间:O(min(m,n)),只在两树重叠的位置才递归下去;非重叠支一步搬走
- 空间:O(min(h1,h2)),递归栈深 = 重叠部分的高度;最坏 O(n)
易错点
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
对称二叉树
LeetCode 101 · 简单 · 沿着 树 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题