617. Merge Two Binary Trees
Problem Description
You are given two binary trees, root1
and root2
. The task is to merge these two trees into a single binary tree following specific rules.
The merging process works by overlaying one tree on top of the other, starting from their root nodes. When you overlay the trees:
- If both trees have a node at the same position, create a new node whose value is the sum of both nodes' values
- If only one tree has a node at a particular position (the other is
null
), use that existing node in the merged tree - If both positions are
null
, the merged tree will also havenull
at that position
The merging must begin from the root nodes and proceed recursively through the entire tree structure. The function should return the root of the newly merged tree.
For example, if root1
has a node with value 3
and root2
has a node with value 5
at the same position, the merged tree will have a node with value 8
at that position. If root1
has a node but root2
doesn't have a corresponding node (or vice versa), the merged tree will simply use the existing node.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: A binary tree is a special type of graph where each node has at most two children and there are no cycles.
Is it a tree?
- Yes: The problem explicitly deals with binary trees (
root1
androot2
). Trees are connected acyclic graphs with a hierarchical structure.
DFS
- Yes: We arrive at DFS (Depth-First Search) as the solution approach.
Conclusion: The flowchart suggests using DFS for this tree merging problem. This makes perfect sense because:
- We need to traverse both trees simultaneously from their roots
- At each node position, we need to process the current nodes before moving to their children
- The recursive nature of DFS naturally handles the tree structure - we merge the current nodes, then recursively merge the left subtrees, then recursively merge the right subtrees
- DFS allows us to build the merged tree in a top-down manner, creating each node and then attaching its merged children
The solution implements DFS through recursion, where at each step we:
- Handle base cases (when one or both nodes are
null
) - Create a new node with the sum of values if both nodes exist
- Recursively merge the left and right subtrees
Intuition
When we need to merge two binary trees, we should think about what happens at each corresponding position in both trees. The key insight is that we need to visit every position that exists in either tree and make a decision about what goes in the merged tree at that position.
Since trees have a recursive structure (each subtree is itself a tree), we can break down the problem recursively. At any given pair of nodes from the two trees, we face one of three scenarios:
- Both nodes exist - we need to create a new node with their summed values
- Only one node exists - we can reuse that entire subtree as-is
- Neither node exists - the merged position is also
null
This naturally leads to a recursive DFS approach. Starting from the roots, we process the current pair of nodes, then recursively handle their left children and right children. The beauty of this approach is that when one tree has null
at a position, we can simply return the other tree's subtree without further processing - this is an optimization since that entire subtree can be reused directly.
The recursion naturally handles the tree traversal for us. Each recursive call deals with one "layer" of the merging problem - merge the current nodes, then delegate the merging of children to recursive calls. The base cases (when one or both nodes are null
) stop the recursion and provide the foundation for building up the merged tree.
Think of it like overlaying two transparent sheets with tree drawings - wherever both sheets have a node, you add the values; wherever only one sheet has nodes, you take those nodes as they are. DFS allows us to systematically visit every position and apply this merging rule.
Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.
Solution Approach
The implementation uses a recursive DFS approach to merge the two binary trees. Let's walk through the solution step by step:
Base Cases: The function first handles the base cases that terminate the recursion:
- If
root1
isNone
, we returnroot2
(which could also beNone
or a valid subtree) - If
root2
isNone
, we returnroot1
(which is guaranteed to be non-null at this point)
These base cases are crucial because they handle situations where one tree has ended but the other continues. When one tree is None
, we can directly use the other tree's subtree without any further merging.
Recursive Case:
When both nodes exist (neither is None
), we:
- Create a new
TreeNode
with value equal toroot1.val + root2.val
- Recursively merge the left children:
node.left = self.mergeTrees(root1.left, root2.left)
- Recursively merge the right children:
node.right = self.mergeTrees(root1.right, root2.right)
- Return the newly created node
Data Structure:
The solution uses the provided TreeNode
class to build the merged tree. Each TreeNode
has:
val
: the integer value stored in the nodeleft
: reference to the left childright
: reference to the right child
Algorithm Pattern: This is a classic tree recursion pattern where we:
- Process the current node
- Recursively process the left subtree
- Recursively process the right subtree
The time complexity is O(min(m, n))
where m
and n
are the number of nodes in the two trees, as we only traverse the overlapping portions of both trees. The space complexity is O(min(m, n))
for the recursion stack in the worst case (when trees are skewed).
The elegance of this solution lies in its simplicity - by handling the base cases properly, the recursive structure naturally builds the merged tree from bottom to top as the call stack unwinds.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Given two binary trees:
Tree 1: Tree 2: 1 2 / \ / \ 3 2 1 3 / \ \ 5 4 7
Step-by-step merging process:
-
Start at roots: Both roots exist (1 and 2)
- Create new node with value 1 + 2 = 3
- Recursively merge left children (3 and 1)
- Recursively merge right children (2 and 3)
-
Left subtree (nodes 3 and 1): Both exist
- Create new node with value 3 + 1 = 4
- Recursively merge left children (5 and null)
- Recursively merge right children (null and 4)
-
Left-left position (5 and null): Only Tree 1 has a node
- Return node 5 as-is (base case: root2 is None)
-
Left-right position (null and 4): Only Tree 2 has a node
- Return node 4 as-is (base case: root1 is None)
-
Right subtree (nodes 2 and 3): Both exist
- Create new node with value 2 + 3 = 5
- Recursively merge left children (null and null)
- Recursively merge right children (null and 7)
-
Right-left position (null and null): Neither has a node
- Return null (base case: root1 is None)
-
Right-right position (null and 7): Only Tree 2 has a node
- Return node 7 as-is (base case: root1 is None)
Resulting merged tree:
3 / \ 4 5 / \ \ 5 4 7
The algorithm efficiently handles all three scenarios: summing overlapping nodes (like 1+2=3), using existing nodes when only one tree has a value (like keeping 5, 4, and 7), and returning null when both positions are empty.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import Optional
9
10
11class Solution:
12 def mergeTrees(
13 self, root1: Optional[TreeNode], root2: Optional[TreeNode]
14 ) -> Optional[TreeNode]:
15 """
16 Merge two binary trees by summing overlapping nodes.
17
18 Args:
19 root1: Root of the first binary tree
20 root2: Root of the second binary tree
21
22 Returns:
23 Root of the merged binary tree
24 """
25 # Base case: if one tree is empty, return the other tree
26 if root1 is None:
27 return root2
28 if root2 is None:
29 return root1
30
31 # Create a new node with the sum of both nodes' values
32 merged_node = TreeNode(root1.val + root2.val)
33
34 # Recursively merge the left subtrees
35 merged_node.left = self.mergeTrees(root1.left, root2.left)
36
37 # Recursively merge the right subtrees
38 merged_node.right = self.mergeTrees(root1.right, root2.right)
39
40 return merged_node
41
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Merges two binary trees by adding corresponding node values.
19 * If one tree has a node where the other doesn't, the existing node is used.
20 *
21 * @param root1 The root of the first binary tree
22 * @param root2 The root of the second binary tree
23 * @return The root of the merged binary tree
24 */
25 public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
26 // Base case: if first tree node is null, return the second tree node
27 if (root1 == null) {
28 return root2;
29 }
30
31 // Base case: if second tree node is null, return the first tree node
32 if (root2 == null) {
33 return root1;
34 }
35
36 // Both nodes exist: create a new node with sum of both values
37 TreeNode mergedNode = new TreeNode(root1.val + root2.val);
38
39 // Recursively merge left subtrees
40 mergedNode.left = mergeTrees(root1.left, root2.left);
41
42 // Recursively merge right subtrees
43 mergedNode.right = mergeTrees(root1.right, root2.right);
44
45 // Return the merged node as the root of this subtree
46 return mergedNode;
47 }
48}
49
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Merges two binary trees by adding corresponding node values
16 * @param root1 - Root of the first binary tree
17 * @param root2 - Root of the second binary tree
18 * @return Root of the merged binary tree
19 */
20 TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
21 // Base case: if one tree is empty, return the other tree
22 if (root1 == nullptr) {
23 return root2;
24 }
25 if (root2 == nullptr) {
26 return root1;
27 }
28
29 // Create a new node with the sum of values from both trees
30 TreeNode* mergedNode = new TreeNode(root1->val + root2->val);
31
32 // Recursively merge the left subtrees
33 mergedNode->left = mergeTrees(root1->left, root2->left);
34
35 // Recursively merge the right subtrees
36 mergedNode->right = mergeTrees(root1->right, root2->right);
37
38 return mergedNode;
39 }
40};
41
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Merges two binary trees by summing overlapping nodes
17 * @param root1 - The root node of the first binary tree
18 * @param root2 - The root node of the second binary tree
19 * @returns The root node of the merged binary tree
20 */
21function mergeTrees(root1: TreeNode | null, root2: TreeNode | null): TreeNode | null {
22 // Base case: both nodes are null
23 if (root1 === null && root2 === null) {
24 return null;
25 }
26
27 // If one tree is null, return the other tree
28 if (root1 === null) {
29 return root2;
30 }
31 if (root2 === null) {
32 return root1;
33 }
34
35 // Recursively merge left subtrees
36 const mergedLeft: TreeNode | null = mergeTrees(root1.left, root2.left);
37
38 // Recursively merge right subtrees
39 const mergedRight: TreeNode | null = mergeTrees(root1.right, root2.right);
40
41 // Create new node with sum of both node values and merged subtrees
42 const mergedNode: TreeNode = new TreeNode(
43 root1.val + root2.val,
44 mergedLeft,
45 mergedRight
46 );
47
48 return mergedNode;
49}
50
Time and Space Complexity
Time Complexity: O(m + n)
where m
is the number of nodes in root1
and n
is the number of nodes in root2
. In the worst case, when both trees are complete and have the same structure, we visit every node from both trees exactly once. In the best case, when one tree is None
, the time complexity is O(1)
as we simply return the other tree.
Space Complexity: O(min(m, n))
for the recursive call stack, where m
and n
are the number of nodes in root1
and root2
respectively. The recursion depth is limited by the smaller tree's height since once we reach a None
node in either tree, the recursion stops for that branch. In the worst case of completely balanced trees with similar structure, the height would be O(log(min(m, n)))
. Additionally, we create O(m + n)
new nodes for the merged tree, making the total space complexity O(m + n)
when considering the output tree. If we only consider auxiliary space (excluding the output), it's O(min(m, n))
for the call stack.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying Original Trees Instead of Creating New Nodes
A common mistake is trying to modify one of the original trees in-place rather than creating new nodes for the merged tree. This can lead to unexpected side effects if the original trees are used elsewhere in the program.
Incorrect approach:
def mergeTrees(self, root1, root2):
if not root1:
return root2
if not root2:
return root1
# Modifying root1 directly - WRONG!
root1.val += root2.val
root1.left = self.mergeTrees(root1.left, root2.left)
root1.right = self.mergeTrees(root1.right, root2.right)
return root1
Why it's problematic:
- Alters the original
root1
tree structure - If
root1
is referenced elsewhere, those references now point to modified data - Violates the principle of immutability for input parameters
Correct solution:
Always create a new TreeNode
when both nodes exist:
merged_node = TreeNode(root1.val + root2.val)
2. Incorrect Base Case Handling
Another pitfall is not properly handling the base cases, particularly when both trees might be None
at the same position.
Incorrect approach:
def mergeTrees(self, root1, root2):
# Missing the case where both could be None
merged_node = TreeNode(0)
if root1:
merged_node.val += root1.val
if root2:
merged_node.val += root2.val
# This creates unnecessary nodes even when both children are None
merged_node.left = self.mergeTrees(
root1.left if root1 else None,
root2.left if root2 else None
)
# ... similar for right
Why it's problematic:
- Creates unnecessary nodes with value 0 when both trees have
None
- Doesn't properly terminate recursion
- Results in a tree with extra nodes that shouldn't exist
Correct solution: Handle base cases first before creating any new nodes:
if root1 is None: return root2 if root2 is None: return root1
3. Deep Copy vs Shallow Reference
When one tree is None
, returning the other tree directly creates a shallow reference. While this is efficient and usually correct, it means the merged tree shares subtrees with the original trees.
Potential issue scenario:
# If you need a completely independent merged tree
def mergeTrees(self, root1, root2):
if not root1:
return root2 # This is a shallow reference, not a copy
if not root2:
return root1 # This is a shallow reference, not a copy
Solution if deep copy is needed:
def deepCopyTree(self, root):
if not root:
return None
new_node = TreeNode(root.val)
new_node.left = self.deepCopyTree(root.left)
new_node.right = self.deepCopyTree(root.right)
return new_node
def mergeTrees(self, root1, root2):
if not root1:
return self.deepCopyTree(root2)
if not root2:
return self.deepCopyTree(root1)
# ... rest of the merge logic
Note: The shallow reference approach in the original solution is generally preferred for efficiency unless there's a specific requirement for complete independence.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!