Merge Two Binary Trees: Recursive DFS in One Pass
You are given two binary trees. Imagine overlaying them on top of each other. Where both trees have a node, the merged node's value is the sum of both values. Where only one tree has a node, that node becomes part of the merged tree as-is.
This is LeetCode 617: Merge Two Binary Trees, and it is one of the best introductions to working with two trees simultaneously. The problem forces you to think about three cases at every position: both nodes exist, only one exists, or neither exists.
Why this problem matters
Most tree problems give you a single tree and ask you to traverse or transform it. Merge Two Binary Trees is different because you are walking two trees at the same time, in lockstep. At every recursive call, you have two nodes (or nulls) to consider, and you need to decide what to do based on which ones exist.
This "parallel traversal" pattern shows up in other tree problems like Same Tree and Symmetric Tree. If you can merge two trees, you already understand the mechanics of comparing or combining them.
The key insight
At every position in the tree, there are only three possibilities:
- Both nodes exist: sum their values and recurse into both pairs of children.
- Only one node exists: return that node (and its entire subtree) directly.
- Neither node exists: return None.
That is the entire algorithm. You walk both trees in the same order (pre-order DFS works well) and apply these three rules at each step. The recursion handles the rest.
The recursive solution
def merge_trees(t1, t2):
if t1 is None:
return t2
if t2 is None:
return t1
t1.val += t2.val
t1.left = merge_trees(t1.left, t2.left)
t1.right = merge_trees(t1.right, t2.right)
return t1
Let's break this down line by line:
if t1 is None: return t2handles the case where the first tree has no node at this position. Whatever the second tree has (a node or None), that is the answer.if t2 is None: return t1handles the mirror case. If the second tree is missing, keep the first tree's node.t1.val += t2.valis the merge step. Both nodes exist, so we add their values. We are reusingt1as the merged tree.- The two recursive calls merge the left children together and the right children together, then attach the results.
The function returns t1, which now contains the fully merged tree.
This solution modifies t1 in place. If you need to preserve both original trees, create a new TreeNode(t1.val + t2.val) instead of modifying t1.val. The recursive structure stays exactly the same.
Step-by-step walkthrough
Let's trace through the merge of two example trees. Tree 1 has root 1, left child 3 (with left grandchild 5), and right child 2. Tree 2 has root 2, left child 1 (with right grandchild 4), and right child 3 (with right grandchild 7).
Step 1: merge(t1=1, t2=2). Both nodes exist, so create node with value 1+2 = 3.
Both roots are non-null. Sum their values to create the merged root. Now recurse into left and right children.
Step 2: merge(t1=3, t2=1). Both exist, create node 3+1 = 4. Attach as left child of 3.
Both left children exist. Sum 3+1 = 4. Continue recursing into their children.
Step 3: merge(t1=5, t2=None). Only t1 exists, so return t1 node (5) directly.
Tree 1 has a left child (5) but Tree 2 does not. When one node is null, just return the other. No further recursion needed here.
Step 4: merge(t1=None, t2=4). Only t2 exists, so return t2 node (4) directly.
Tree 1 has no right child but Tree 2 does (4). Return the existing node. The left subtree is now complete.
Step 5: merge(t1=2, t2=3). Both exist, create node 2+3 = 5. Attach as right child of 3.
Back up to the root and recurse right. Both right children exist. Sum 2+3 = 5. Continue into their children.
Step 6: Left children are both None. Right: merge(t1=None, t2=7). Return 7.
Node 5's left children are both null (return None). For the right, only Tree 2 has a child (7). Return it directly.
Step 7: All done. The merged tree is complete.
Every node pair has been visited. Overlapping nodes were summed, and lone nodes were kept as-is. The merge is complete.
Notice how the recursion naturally handles asymmetry. When one tree has a child but the other does not, we just return the existing subtree. There is no need for special-case logic beyond the two null checks at the top of the function.
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(min(m, n)) where m and n are the number of nodes in each tree |
| Space | O(min(h1, h2)) call stack, where h1 and h2 are the heights |
Time: we visit each overlapping node exactly once. In the worst case, both trees have the same shape and we visit all nodes. But if one tree is smaller, we stop recursing once we hit a null in the smaller tree and just attach the remaining subtree from the larger tree.
Space: the call stack depth equals the height of the shorter tree's overlap with the longer one. For a balanced tree, this is O(log n). For a completely skewed tree, it could be O(n).
If you create new nodes instead of modifying t1 in place, the space complexity includes O(min(m, n)) extra nodes for the merged tree. The algorithmic complexity does not change.
Edge cases to watch for
- Both trees are None: the first null check returns
t2, which is also None. Correct. - One tree is completely empty: if
t1is None, you return the entiret2tree. Ift2is None, you return the entiret1tree. No recursion needed. - Trees with different shapes: the null checks handle this naturally. Wherever one tree has a subtree and the other does not, the existing subtree is reused without further traversal.
- Single-node trees: both have just a root. The values are summed, and both recursive calls hit None immediately.
The building blocks
This problem uses two fundamental building blocks:
1. Recursive null base case. The skeleton of "if null, return; otherwise process and recurse into children" is the same pattern you see in Maximum Depth, Invert Binary Tree, Same Tree, and dozens of others. Here the base case returns the other tree's node (or None if both are null), rather than returning 0 or True.
2. Parallel tree traversal. Instead of walking one tree, you walk two trees in lockstep. Each recursive call takes a pair of nodes (t1, t2) and processes them together. This same pattern appears in Same Tree (comparing values) and Symmetric Tree (comparing mirror positions). The merge operation is just one specific way to combine the two nodes.
# The general pattern for parallel tree traversal:
def solve(t1, t2):
if t1 is None and t2 is None:
return None
if t1 is None:
return handle_only_t2(t2)
if t2 is None:
return handle_only_t1(t1)
# both exist: combine and recurse
result = combine(t1, t2)
result.left = solve(t1.left, t2.left)
result.right = solve(t1.right, t2.right)
return result
For Merge Two Binary Trees, combine means summing the values. For Same Tree, it means checking equality. The skeleton stays the same.
From understanding to recall
You just traced through a recursive merge and saw exactly how two trees are walked in parallel. The logic is clean: two null checks at the top, a sum in the middle, and two recursive calls. It makes sense right now.
But understanding is not the same as recall. In an interview, you need to write this from memory under pressure. Can you reconstruct the three cases (both null, one null, both present) without looking? Can you remember which node to return in each null case?
Spaced repetition builds that recall. You practice writing the solution from scratch at increasing intervals. First after a day, then three days, then a week. Each rep strengthens the pattern until the parallel tree traversal skeleton becomes automatic.
The parallel traversal pattern and the recursive null base case show up across many tree problems. Drilling them individually with spaced repetition is far more efficient than solving random problems and hoping the connections stick.
Related posts
- Invert Binary Tree - Another tree transformation problem using the same recursive null base case pattern, but swapping children instead of merging
- Same Tree - Uses the parallel tree traversal pattern to compare two trees for equality instead of merging them
- Symmetric Tree - Walks two subtrees in mirror order using the same dual-node recursion approach