Facebook Pixel

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 have null 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 and root2). 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:

  1. We need to traverse both trees simultaneously from their roots
  2. At each node position, we need to process the current nodes before moving to their children
  3. 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
  4. 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Both nodes exist - we need to create a new node with their summed values
  2. Only one node exists - we can reuse that entire subtree as-is
  3. 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 is None, we return root2 (which could also be None or a valid subtree)
  • If root2 is None, we return root1 (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:

  1. Create a new TreeNode with value equal to root1.val + root2.val
  2. Recursively merge the left children: node.left = self.mergeTrees(root1.left, root2.left)
  3. Recursively merge the right children: node.right = self.mergeTrees(root1.right, root2.right)
  4. 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 node
  • left: reference to the left child
  • right: 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 Evaluator

Example 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:

  1. 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)
  2. 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)
  3. Left-left position (5 and null): Only Tree 1 has a node

    • Return node 5 as-is (base case: root2 is None)
  4. Left-right position (null and 4): Only Tree 2 has a node

    • Return node 4 as-is (base case: root1 is None)
  5. 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)
  6. Right-left position (null and null): Neither has a node

    • Return null (base case: root1 is None)
  7. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More