Merge Two Binary Trees
EasyPrompt
You are given two binary trees root1 and root2.
Imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not. You need to merge the two trees into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of the new tree.
Return the merged tree.
Note: The merging process must start from the root nodes of both trees.
Example 1:
Input: root1 = [1,3,2,5], root2 = [2,1,3,null,4,null,7]
Output: [3,4,5,5,4,null,7]Example 2:
Input: root1 = [1], root2 = [1,2]
Output: [2,2]
Constraints:
- The number of nodes in both trees is in the range
[0, 2000]. -104 <= Node.val <= 104
Approaches
3 approaches with complexity analysis and trade-offs.
This approach avoids recursion by using a stack to perform a pre-order traversal of the trees. It modifies one of the trees (e.g., tree1) in-place to store the merged result, which saves space compared to creating a new tree. This is useful for avoiding stack overflow on extremely deep trees.
Algorithm
- If
root1is null, returnroot2. - Create a
Stackand push aTreeNode[]pair containing[root1, root2]. - Loop while the stack is not empty:
- Pop the pair
[t1, t2]. - If
t2is null, continue to the next iteration. - Add
t2.valtot1.val. - For the left children: If
t1.leftis null, sett1.left = t2.left. Otherwise, push[t1.left, t2.left]to the stack. - For the right children: If
t1.rightis null, sett1.right = t2.right. Otherwise, push[t1.right, t2.right]to the stack.
- Pop the pair
- Return
root1.
Walkthrough
The core idea is to use a stack to keep track of pairs of nodes from tree1 and tree2 that need to be processed. We will merge tree2 into tree1.
- First, handle the edge case where one of the root nodes is null. If
root1is null, we can simply returnroot2. - Push the root pair
[root1, root2]onto the stack. - While the stack is not empty, pop a pair
[t1, t2]. - If
t2is null, there's nothing to merge for this pair, so we continue. - Sum the values:
t1.val += t2.val. - Now, consider the left children. If
t1has a left child, we need to checkt2's left child. Ift2also has a left child, we push the pair[t1.left, t2.left]to the stack for future processing. Ift1does not have a left child butt2does, we can directly attacht2's left subtree tot1by settingt1.left = t2.left. - The same logic is applied to the right children.
- After the loop finishes,
root1will be the root of the merged tree.
import java.util.Stack; class Solution { public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { if (root1 == null) { return root2; } // We will merge root2 into root1 Stack<TreeNode[]> stack = new Stack<>(); stack.push(new TreeNode[]{root1, root2}); while (!stack.isEmpty()) { TreeNode[] t = stack.pop(); // No need to merge if t[1] is null, as t[0] remains as is. if (t[0] == null || t[1] == null) { continue; } // Add t[1]'s value to t[0]'s t[0].val += t[1].val; // Process left children if (t[0].left == null) { t[0].left = t[1].left; } else { stack.push(new TreeNode[]{t[0].left, t[1].left}); } // Process right children if (t[0].right == null) { t[0].right = t[1].right; } else { stack.push(new TreeNode[]{t[0].right, t[1].right}); } } return root1; }}Complexity
Time
O(M), where M is the number of overlapping nodes in the two trees. We only push pairs to the stack where both nodes exist, so we only iterate over the overlapping parts.
Space
O(W), where W is the maximum width of the trees. In the worst case of a complete binary tree, the width can be O(N), where N is the number of nodes. In the best case of a skewed tree, the space is O(1).
Trade-offs
Pros
Avoids recursion, preventing potential stack overflow errors for very deep trees.
More space-efficient than creating a new tree since it modifies one in-place.
Cons
Modifies one of the original input trees.
The code can be slightly more complex to write and understand compared to the recursive version.
Worst-case space complexity for the stack can be O(N) for wide, bushy trees.
This approach involves creating a completely new binary tree to store the merged result. A recursive function traverses both trees simultaneously. For each pair of corresponding nodes, it creates a new node in the result tree based on the merge rule. This method is conceptually simple but has a higher space cost.
Algorithm
- Define a recursive function
merge(node1, node2)that returns a newTreeNode. - Base Case 1: If
node1is null, returnnode2. - Base Case 2: If
node2is null, returnnode1. - Recursive Step: If both nodes are non-null:
- Create a new
TreeNodewith a value ofnode1.val + node2.val. - Set the new node's left child to the result of
merge(node1.left, node2.left). - Set the new node's right child to the result of
merge(node1.right, node2.right). - Return the new node.
- Create a new
- The initial call is
merge(root1, root2).
Walkthrough
We define a recursive helper function, say merge(node1, node2). The function's logic is as follows:
-
Base Cases:
- If
node1is null, it means there's no corresponding node in the first tree. We simply returnnode2(which could be a subtree or null), as it will be the node in the merged tree. - Similarly, if
node2is null, we returnnode1.
- If
-
Recursive Step:
- If both
node1andnode2are non-null, they overlap. We create a newTreeNodewhose value is the sum ofnode1.valandnode2.val. - The left child of this new node is determined by a recursive call:
newNode.left = merge(node1.left, node2.left). - The right child is determined similarly:
newNode.right = merge(node1.right, node2.right). - The newly created node is then returned.
- If both
The initial call to this function will be merge(root1, root2), which will build and return the root of the new merged tree.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { if (root1 == null) { return root2; } if (root2 == null) { return root1; } // Both nodes are non-null, create a new node with the sum TreeNode mergedNode = new TreeNode(root1.val + root2.val); // Recursively merge the left and right subtrees mergedNode.left = mergeTrees(root1.left, root2.left); mergedNode.right = mergeTrees(root1.right, root2.right); return mergedNode; }}Complexity
Time
O(N), where N is the total number of nodes in the final merged tree. We need to visit every node that will be part of the new tree to create it.
Space
O(N), where N is the total number of nodes in the final merged tree. This is because we are creating a new tree of size N. The recursion stack also uses space, up to O(H) where H is the maximum height of the trees, but this is dominated by the O(N) space for the new tree itself.
Trade-offs
Pros
It does not modify the original input trees, which can be a desirable property.
The logic is straightforward and easy to understand.
Cons
Uses significant extra space to create an entirely new tree, making it the least space-efficient solution.
This is the most common and often most efficient approach. It uses recursion to traverse the trees, but instead of creating new nodes, it modifies one of the existing trees (e.g., tree1) to become the merged tree. This significantly reduces space consumption while maintaining clean, readable code.
Algorithm
- Define a recursive function
merge(t1, t2)that returns the mergedTreeNode. - Base Case 1: If
t1is null, returnt2. - Base Case 2: If
t2is null, returnt1. - Recursive Step: If both nodes are non-null:
- Update
t1.valby addingt2.valto it. - Recursively call
merge(t1.left, t2.left)and assign the result tot1.left. - Recursively call
merge(t1.right, t2.right)and assign the result tot1.right. - Return the modified
t1node.
- Update
- The initial call is
merge(root1, root2).
Walkthrough
The function mergeTrees(t1, t2) returns the merged tree node by modifying t1 in-place.
-
Base Cases:
- If
t1is null, it means there's no node in the first tree at this position. The result of the merge is simply the subtree from the second tree, so we returnt2. This effectively attachest2's subtree to the parent in the first tree. - If
t2is null, there's nothing to merge from the second tree. We can just return the existing nodet1as is.
- If
-
Recursive Step:
- If both
t1andt2are non-null, we are at an overlapping node. We update the value oft1by addingt2.valto it:t1.val += t2.val. - We then recursively merge the left subtrees and update
t1's left child with the result:t1.left = mergeTrees(t1.left, t2.left). - Similarly, we merge the right subtrees:
t1.right = mergeTrees(t1.right, t2.right). - Finally, we return the modified
t1node.
- If both
The initial call mergeTrees(root1, root2) will return the root of the fully merged tree, which is the modified root1.
class Solution { public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { if (t1 == null) { return t2; } if (t2 == null) { return t1; } // Both nodes exist, merge t2 into t1 t1.val += t2.val; t1.left = mergeTrees(t1.left, t2.left); t1.right = mergeTrees(t1.right, t2.right); return t1; }}Complexity
Time
O(M), where M is the number of nodes in the smaller of the two trees (or more accurately, the number of overlapping nodes). We only make recursive calls for overlapping nodes.
Space
O(H), where H is the maximum height of the two trees. This space is used by the recursion call stack. For a balanced tree, this is O(log N), and for a skewed tree, it's O(N).
Trade-offs
Pros
Very space-efficient as it doesn't create a new tree.
The code is concise, elegant, and highly readable.
Often has the best average-case space complexity (O(log N) for balanced trees).
Cons
It modifies one of the original input trees.
Can lead to a stack overflow error if the trees are extremely deep and skewed, though this is rare under typical constraints.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public TreeNode mergeTrees ( TreeNode root1 , TreeNode root2 ) { if ( root1 == null ) { return root2 ; } if ( root2 == null ) { return root1 ; } TreeNode node = new TreeNode ( root1 . val + root2 . val ); node . left = mergeTrees ( root1 . left , root2 . left ); node . right = mergeTrees ( root1 . right , root2 . right ); return node ; } }/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root1 * @param {TreeNode} root2 * @return {TreeNode} */ var mergeTrees = function (root1, root2) { if (!root1) { return root2; } if (!root2) { return root1; } const node = new TreeNode(root1.val + root2.val); node.left = mergeTrees(root1.left, root2.left); node.right = mergeTrees(root1.right, root2.right); return node; };/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: TreeNode * mergeTrees ( TreeNode * root1 , TreeNode * root2 ) { if ( ! root1 ) return root2 ; if ( ! root2 ) return root1 ; TreeNode * node = new TreeNode ( root1 -> val + root2 -> val ); node -> left = mergeTrees ( root1 -> left , root2 -> left ); node -> right = mergeTrees ( root1 -> right , root2 -> right ); return node ; } };# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution : def mergeTrees ( self , root1 : Optional [ TreeNode ], root2 : Optional [ TreeNode ] ) -> Optional [ TreeNode ]: if root1 is None : return root2 if root2 is None : return root1 node = TreeNode ( root1 . val + root2 . val ) node . left = self . mergeTrees ( root1 . left , root2 . left ) node . right = self . mergeTrees ( root1 . right , root2 . right ) return nodeVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.