617. Merge Two Binary Trees - Explanation
Description
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 = [2]
Output: [3]Example 3:
Input: root1 = [], root2 = [2]
Output: [2]Constraints:
0 <= The number of nodes in both the trees <= 2000.-10,000 <= Node.val <= 10,000
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Trees - Understanding tree node structure with left and right children
- Depth First Search (DFS) - Recursively or iteratively traversing tree structures
- Recursion - Solving problems by breaking them into smaller subproblems with base cases
- Stack - Using explicit stacks to convert recursive solutions to iterative ones
1. Depth First Search (Creating New Tree)
Intuition
To merge two trees, we traverse both trees simultaneously. At each position, if both trees have a node, we create a new node with the sum of their values. If only one tree has a node at a position, we use that node's value. We recursively build the left and right subtrees the same way. This approach creates an entirely new tree without modifying the inputs.
Algorithm
- If both
root1androot2arenull, returnnull. - Get the values from each tree (use
0if the node isnull). - Create a new node with the sum of the two values.
- Recursively merge the left children and assign to
root.left. - Recursively merge the right children and assign to
root.right. - Return the new root 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 not root1 and not root2:
return None
v1 = root1.val if root1 else 0
v2 = root2.val if root2 else 0
root = TreeNode(v1 + v2)
root.left = self.mergeTrees(root1.left if root1 else None, root2.left if root2 else None)
root.right = self.mergeTrees(root1.right if root1 else None, root2.right if root2 else None)
return rootTime & Space Complexity
- Time complexity:
- Space complexity:
- space for recursion stack.
- space for the output.
Where and are the number of nodes in the given trees.
2. Depth First Search (In Place)
Intuition
Instead of creating new nodes, we can modify the first tree in place. If one tree is missing at a position, we simply return the other tree's subtree. If both exist, we add the second tree's value to the first tree's node and recursively merge the children. This saves memory by reusing existing nodes.
Algorithm
- If
root1isnull, returnroot2. - If
root2isnull, returnroot1. - Add
root2.valtoroot1.val. - Recursively merge the left subtrees and assign to
root1.left. - Recursively merge the right subtrees and assign to
root1.right. - Return
root1.
# 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 not root1:
return root2
if not root2:
return root1
root1.val += root2.val
root1.left = self.mergeTrees(root1.left, root2.left)
root1.right = self.mergeTrees(root1.right, root2.right)
return root1Time & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
Where and are the number of nodes in the given trees.
3. Iterative DFS (Creating New Tree)
Intuition
We can avoid recursion by using an explicit stack to traverse both trees. We push triplets of (node1, node2, merged_node) onto the stack. For each triplet, we create children for the merged node based on whether the corresponding children exist in either input tree. This creates a new tree while simulating the recursive approach iteratively.
Algorithm
- Handle base cases where one or both trees are
null. - Create a new root node with the sum of both root values.
- Initialize a stack with the triplet (
root1,root2,root). - While the stack is not empty:
- Pop (
node1,node2,node). - For left children: if either exists, create a merged left child and push to stack if both exist.
- For right children: similarly handle and push if both exist.
- Pop (
- Return the merged root.
# 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 not root1:
return root2
if not root2:
return root1
root = TreeNode(root1.val + root2.val)
stack = [(root1, root2, root)]
while stack:
node1, node2, node = stack.pop()
if node1.left and node2.left:
node.left = TreeNode(node1.left.val + node2.left.val)
stack.append((node1.left, node2.left, node.left))
elif not node1.left:
node.left = node2.left
else:
node.left = node1.left
if node1.right and node2.right:
node.right = TreeNode(node1.right.val + node2.right.val)
stack.append((node1.right, node2.right, node.right))
elif not node1.right:
node.right = node2.right
else:
node.right = node1.right
return rootTime & Space Complexity
- Time complexity:
- Space complexity:
- space for the stack.
- space for the output.
Where and are the number of nodes in the given trees.
4. Iterative DFS (In Place)
Intuition
Similar to the recursive in-place approach, we can modify the first tree iteratively using a stack. We push pairs of nodes from both trees. When both nodes exist, we add values and continue processing children. If only one tree has a child at a position, we directly attach that subtree to the first tree. This avoids creating new nodes.
Algorithm
- Handle base cases where one or both trees are
null. - Initialize a stack with the pair (
root1,root2). - While the stack is not empty:
- Pop (
node1,node2). Skip if either isnull. - Add
node2.valtonode1.val. - If both have left children, push them to the stack. Otherwise, if
node1has no left child, attachnode2.left. - Similarly handle right children.
- Pop (
- Return
root1.
# 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 not root1:
return root2
if not root2:
return root1
stack = [(root1, root2)]
while stack:
node1, node2 = stack.pop()
if not node1 or not node2:
continue
node1.val += node2.val
if node1.left and node2.left:
stack.append((node1.left, node2.left))
elif not node1.left:
node1.left = node2.left
if node1.right and node2.right:
stack.append((node1.right, node2.right))
elif not node1.right:
node1.right = node2.right
return root1Time & Space Complexity
- Time complexity:
- Space complexity: for the stack.
Where and are the number of nodes in the given trees.
Common Pitfalls
Not Handling the Case When One Tree is Null
When one of the input trees is null at a position while the other is not, you should return the non-null subtree directly. Failing to handle this base case properly leads to null pointer exceptions or missing nodes in the result.
Modifying the Input Trees Unintentionally
When using the in-place approach, the first tree is modified to become the merged result. If the original trees need to be preserved for other purposes, this causes unexpected side effects. Use the approach that creates a new tree if the inputs must remain unchanged.
Sign in to join the discussion