Facebook Pixel

623. Add One Row to Tree

Problem Description

You are given a binary tree and need to insert a new row of nodes at a specific depth. The task requires adding nodes with a given value val at depth depth.

The problem follows these rules:

  1. Tree depth counting: The root node is at depth 1, its children are at depth 2, and so on.

  2. Standard insertion (depth > 1):

    • Find all nodes at depth depth - 1
    • For each of these nodes, create two new nodes with value val
    • The new left node becomes the current node's new left child, and the current node's original left subtree becomes the left child of this new node
    • The new right node becomes the current node's new right child, and the current node's original right subtree becomes the right child of this new node
  3. Special case (depth = 1):

    • Create a new root node with value val
    • The entire original tree becomes the left subtree of this new root
    • The new root's right subtree remains empty

For example, if you have a tree and want to add a row with value 5 at depth 2:

  • Find all nodes at depth 1 (just the root)
  • Create two new nodes with value 5
  • Insert them as the root's new left and right children
  • The root's original children become the children of these new nodes

The solution uses depth-first search (DFS) to traverse the tree. When it reaches nodes at depth - 1, it performs the insertion by creating new TreeNode objects with the specified value and rearranging the subtree connections according to the rules above.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: While this is specifically a binary tree problem, a tree is a special type of graph (an acyclic connected graph). The problem involves nodes with parent-child relationships, which are essentially vertices and edges.

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree. We have a root node, and each node can have at most two children (left and right).

DFS

  • Yes: This leads us to DFS (Depth-First Search) as the recommended approach.

Conclusion: The flowchart suggests using DFS for this tree traversal problem.

This makes perfect sense for "Add One Row to Tree" because:

  1. We need to traverse the tree to find all nodes at a specific depth (depth - 1)
  2. DFS allows us to track the current depth as we recursively traverse down the tree
  3. When we reach the target depth, we can perform the insertion operation
  4. The recursive nature of DFS naturally handles the tree structure, visiting each node and its subtrees in a systematic way

The solution implements DFS through a recursive helper function that:

  • Tracks the current depth as it traverses
  • When it reaches depth - 1, it performs the row insertion
  • Otherwise, it continues traversing to the left and right children
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to add a row at a specific depth, the key insight is that we don't actually need to reach that exact depth - we need to reach the nodes one level above it (at depth - 1). Why? Because these are the nodes that will become the parents of our new row.

Think of it like inserting a new floor in a building between existing floors. You don't work on the new floor itself; you work from the floor above it to build the connections downward.

The natural approach is to traverse the tree while keeping track of our current depth. As we go deeper into the tree, we increment our depth counter. When we reach depth - 1, we've found the nodes where we need to perform the insertion.

For each node at depth - 1, we need to:

  1. Create two new nodes with value val
  2. Make these new nodes the immediate children of the current node
  3. Preserve the original subtrees by attaching them to the new nodes

The elegant part is how we handle the connections. For the left side, we create TreeNode(val, root.left, None) - this new node takes the original left subtree as its own left child. Similarly, for the right side, we create TreeNode(val, None, root.right) - this new node takes the original right subtree as its own right child.

The special case of depth == 1 requires different handling because there's no "depth 0" to work from. In this case, we simply create a new root that sits above everything else, with the entire original tree becoming its left subtree.

Using DFS (implemented recursively) is natural here because:

  • We can easily track depth through the recursion parameter
  • We visit every node at the target depth systematically
  • The recursive calls handle the tree traversal automatically
  • We can stop going deeper once we've performed the insertion (no need to traverse the entire tree)

Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.

Solution Approach

The implementation uses a recursive DFS approach with a helper function to traverse the tree and perform the insertion at the correct depth.

Main Function Structure:

def addOneRow(self, root, val, depth):
    if depth == 1:
        return TreeNode(val, root)
    dfs(root, 1)
    return root

The main function first handles the special case where depth == 1. In this case, we create a new root node with the original tree as its left subtree and return it immediately.

DFS Helper Function:

def dfs(root, d):
    if root is None:
        return
    if d == depth - 1:
        root.left = TreeNode(val, root.left, None)
        root.right = TreeNode(val, None, root.right)
        return
    dfs(root.left, d + 1)
    dfs(root.right, d + 1)

The recursive DFS function takes two parameters:

  • root: The current node being processed
  • d: The current depth in the tree

Algorithm Steps:

  1. Base Case: If the current node is None, we return immediately (nothing to process).

  2. Target Depth Check: When d == depth - 1, we've reached the parent nodes of where we want to insert the new row:

    • Create a new left child: TreeNode(val, root.left, None)
      • This new node has value val
      • Its left child is the original left subtree
      • Its right child is None
    • Create a new right child: TreeNode(val, None, root.right)
      • This new node has value val
      • Its left child is None
      • Its right child is the original right subtree
    • Return after insertion (no need to go deeper)
  3. Recursive Traversal: If we haven't reached the target depth yet:

    • Recursively call dfs on the left child with d + 1
    • Recursively call dfs on the right child with d + 1

Key Implementation Details:

  • The TreeNode constructor accepts three parameters: TreeNode(val, left, right), allowing us to create and link nodes in a single statement.

  • We start the DFS with d = 1 since the root is at depth 1.

  • The algorithm only traverses nodes up to depth - 1, making it efficient as it doesn't visit unnecessary nodes below the insertion point.

  • Time Complexity: O(n) where n is the number of nodes up to the insertion depth.

  • Space Complexity: O(h) where h is the height of the tree, due to the recursion call stack.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through inserting a row with value 5 at depth 2 in this tree:

Initial tree:
      1
     / \
    2   3
   /
  4

Step 1: Check special case

  • Since depth = 2 (not 1), we proceed with normal DFS

Step 2: Start DFS from root

  • Call dfs(node_1, d=1)
  • Current depth d = 1, target is depth - 1 = 1
  • We've found our insertion point!

Step 3: Perform insertion at node 1

  • Create new left node: TreeNode(5, node_2, None)
    • This new node has value 5
    • Takes the original left subtree (node 2 and its children) as its left child
  • Create new right node: TreeNode(5, None, node_3)
    • This new node has value 5
    • Takes the original right subtree (node 3) as its right child

Step 4: Rearrange connections

Before insertion:
  1.left2
  1.right3

After insertion:
  1.left → new_node_5 (left)
  1.right → new_node_5 (right)
  new_node_5(left).left2
  new_node_5(right).right3

Final tree:

      1
     / \
    5   5
   /     \
  2       3
 /
4

The algorithm successfully inserted a new row at depth 2. Note how:

  • Both new nodes have value 5
  • The original left subtree (2→4) is preserved under the left new node
  • The original right subtree (3) is preserved under the right new node
  • We never needed to traverse to depth 2 or beyond - we did all the work at depth 1

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 addOneRow(
13        self, root: Optional[TreeNode], val: int, depth: int
14    ) -> Optional[TreeNode]:
15        """
16        Adds a row of nodes with given value at the specified depth in a binary tree.
17      
18        Args:
19            root: The root of the binary tree
20            val: The value to be inserted in the new row
21            depth: The depth at which to insert the new row (1-indexed)
22      
23        Returns:
24            The root of the modified tree
25        """
26      
27        # Special case: if depth is 1, create a new root with the original tree as left child
28        if depth == 1:
29            new_root = TreeNode(val, root, None)
30            return new_root
31      
32        def dfs(node: Optional[TreeNode], current_depth: int) -> None:
33            """
34            Performs depth-first search to find and modify nodes at target depth.
35          
36            Args:
37                node: Current node being processed
38                current_depth: Current depth in the tree (1-indexed)
39            """
40            # Base case: reached a null node
41            if node is None:
42                return
43          
44            # Found the parent level of where we need to insert
45            if current_depth == depth - 1:
46                # Create new left node with original left subtree as its left child
47                original_left = node.left
48                node.left = TreeNode(val, original_left, None)
49              
50                # Create new right node with original right subtree as its right child
51                original_right = node.right
52                node.right = TreeNode(val, None, original_right)
53                return
54          
55            # Continue traversing deeper into the tree
56            dfs(node.left, current_depth + 1)
57            dfs(node.right, current_depth + 1)
58      
59        # Start DFS from root at depth 1
60        dfs(root, 1)
61        return root
62
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    // Instance variables to store the value to insert and target depth
18    private int valueToInsert;
19    private int targetDepth;
20
21    /**
22     * Adds a row of nodes with the given value at the specified depth in the binary tree.
23     * 
24     * @param root The root of the binary tree
25     * @param val The value to be inserted in the new row
26     * @param depth The depth at which to insert the new row (1-indexed)
27     * @return The root of the modified tree
28     */
29    public TreeNode addOneRow(TreeNode root, int val, int depth) {
30        // Special case: if depth is 1, create a new root with the original tree as left child
31        if (depth == 1) {
32            return new TreeNode(val, root, null);
33        }
34      
35        // Store parameters as instance variables for use in DFS
36        this.valueToInsert = val;
37        this.targetDepth = depth;
38      
39        // Perform depth-first search to insert the new row
40        performDFS(root, 1);
41      
42        return root;
43    }
44
45    /**
46     * Performs depth-first search to find nodes at the parent level of the target depth
47     * and inserts new nodes between parents and their children.
48     * 
49     * @param currentNode The current node being processed
50     * @param currentDepth The current depth in the tree (1-indexed)
51     */
52    private void performDFS(TreeNode currentNode, int currentDepth) {
53        // Base case: if current node is null, return
54        if (currentNode == null) {
55            return;
56        }
57      
58        // If we've reached the parent level of the target depth
59        if (currentDepth == targetDepth - 1) {
60            // Create new left node with current left child as its left child
61            TreeNode newLeftNode = new TreeNode(valueToInsert, currentNode.left, null);
62          
63            // Create new right node with current right child as its right child
64            TreeNode newRightNode = new TreeNode(valueToInsert, null, currentNode.right);
65          
66            // Update current node's children to point to the new nodes
67            currentNode.left = newLeftNode;
68            currentNode.right = newRightNode;
69          
70            // No need to continue deeper in the tree
71            return;
72        }
73      
74        // Recursively process left and right subtrees
75        performDFS(currentNode.left, currentDepth + 1);
76        performDFS(currentNode.right, currentDepth + 1);
77    }
78}
79
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 {
13private:
14    int targetVal;      // Value to be inserted in new row
15    int targetDepth;    // Target depth where new row should be added
16  
17public:
18    /**
19     * Adds a new row of nodes with given value at specified depth
20     * @param root: Root of the binary tree
21     * @param val: Value for new nodes to be added
22     * @param depth: Depth at which to add the new row (1-indexed)
23     * @return: Root of the modified tree
24     */
25    TreeNode* addOneRow(TreeNode* root, int val, int depth) {
26        // Special case: if depth is 1, create new root with original tree as left child
27        if (depth == 1) {
28            return new TreeNode(val, root, nullptr);
29        }
30      
31        // Initialize member variables for DFS traversal
32        this->targetVal = val;
33        this->targetDepth = depth;
34      
35        // Perform DFS to add new row at specified depth
36        dfsHelper(root, 1);
37      
38        return root;
39    }
40  
41private:
42    /**
43     * Helper function to traverse tree and insert new row at target depth
44     * @param node: Current node being processed
45     * @param currentDepth: Current depth in the tree (1-indexed)
46     */
47    void dfsHelper(TreeNode* node, int currentDepth) {
48        // Base case: null node
49        if (!node) {
50            return;
51        }
52      
53        // If we're at the parent level of target depth, insert new row
54        if (currentDepth == targetDepth - 1) {
55            // Create new left node with original left subtree as its left child
56            TreeNode* newLeft = new TreeNode(targetVal, node->left, nullptr);
57          
58            // Create new right node with original right subtree as its right child
59            TreeNode* newRight = new TreeNode(targetVal, nullptr, node->right);
60          
61            // Update current node's children to point to new nodes
62            node->left = newLeft;
63            node->right = newRight;
64          
65            return;
66        }
67      
68        // Continue DFS traversal to next level
69        dfsHelper(node->left, currentDepth + 1);
70        dfsHelper(node->right, currentDepth + 1);
71    }
72};
73
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 * Adds a row of nodes with the given value at the specified depth in the binary tree.
17 * 
18 * @param root - The root node of the binary tree
19 * @param val - The value to be assigned to the new nodes
20 * @param depth - The depth at which to insert the new row (1-indexed)
21 * @returns The modified tree with the new row added
22 */
23function addOneRow(root: TreeNode | null, val: number, depth: number): TreeNode | null {
24    // Special case: if depth is 1, create a new root with the original tree as left child
25    if (depth === 1) {
26        return new TreeNode(val, root, null);
27    }
28  
29    /**
30     * Performs depth-first search to find the target depth and insert new nodes.
31     * 
32     * @param node - The current node being processed
33     * @param currentDepth - The current depth in the tree (1-indexed)
34     */
35    function dfs(node: TreeNode | null, currentDepth: number): void {
36        // Base case: if node is null, nothing to process
37        if (!node) {
38            return;
39        }
40      
41        // When we reach the parent level of the target depth, insert new nodes
42        if (currentDepth === depth - 1) {
43            // Create new left node with original left subtree as its left child
44            node.left = new TreeNode(val, node.left, null);
45            // Create new right node with original right subtree as its right child
46            node.right = new TreeNode(val, null, node.right);
47            return;
48        }
49      
50        // Recursively traverse left and right subtrees
51        dfs(node.left, currentDepth + 1);
52        dfs(node.right, currentDepth + 1);
53    }
54  
55    // Start DFS from root at depth 1
56    dfs(root, 1);
57  
58    return root;
59}
60

Time and Space Complexity

Time Complexity: O(n) where n is the number of nodes in the binary tree.

The algorithm uses depth-first search (DFS) to traverse the tree. In the worst case, it needs to visit every node in the tree when the target depth is at or near the bottom of the tree. Each node is visited exactly once, and for each node, we perform constant time operations (checking the depth, creating new nodes if needed, and making recursive calls). Therefore, the time complexity is linear with respect to the number of nodes.

Space Complexity: O(h + m) where h is the height of the tree and m is the number of new nodes added.

The space complexity consists of two components:

  1. Recursion Stack: The DFS recursion can go as deep as the height of the tree in the worst case (for a skewed tree, this could be O(n), for a balanced tree it would be O(log n)). This contributes O(h) to the space complexity.
  2. New Nodes Created: The algorithm creates new nodes at the specified depth. In the worst case, if we're adding a row at depth d, we create 2^(d-1) new nodes (one for each existing node at depth d-1). This contributes O(m) where m = 2^(d-1) in the worst case.

For a balanced binary tree, the overall space complexity would be O(log n + 2^(d-1)). For a skewed tree, it would be O(n) due to the recursion stack depth.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Handling of the Depth = 1 Special Case

A common mistake is forgetting that when depth = 1, the entire original tree should become the left subtree of the new root, not the right subtree. Many developers intuitively might place it as the right child or try to split the original tree.

Incorrect Implementation:

# Wrong: placing original tree as right child
if depth == 1:
    return TreeNode(val, None, root)  # ❌ Wrong

# Wrong: trying to create two children at depth 1
if depth == 1:
    new_root = TreeNode(val)
    new_root.left = TreeNode(val, root.left, None)
    new_root.right = TreeNode(val, None, root.right)
    return new_root  # ❌ Wrong

Correct Implementation:

if depth == 1:
    return TreeNode(val, root, None)  # ✅ Original tree as left child

2. Off-by-One Error in Depth Calculation

Another frequent pitfall is confusion about when to stop the traversal. The algorithm needs to find nodes at depth - 1 to insert children at depth, but developers might mistakenly check for depth directly.

Incorrect Implementation:

def dfs(node, current_depth):
    if node is None:
        return
  
    # Wrong: checking for depth instead of depth - 1
    if current_depth == depth:  # ❌ Wrong
        # This would try to insert at the wrong level
        node.left = TreeNode(val, node.left, None)
        node.right = TreeNode(val, None, node.right)
        return

Correct Implementation:

def dfs(node, current_depth):
    if node is None:
        return
  
    # Correct: checking for depth - 1
    if current_depth == depth - 1:  # ✅ Correct
        node.left = TreeNode(val, node.left, None)
        node.right = TreeNode(val, None, node.right)
        return

3. Modifying Node References Before Saving Them

A subtle but critical error occurs when developers modify the node's children before saving the original references, leading to loss of the original subtrees.

Incorrect Implementation:

if current_depth == depth - 1:
    # Wrong: modifying before saving original references
    node.left = TreeNode(val)  # ❌ Lost original left subtree!
    node.left.left = node.left  # This now points to the new node, not original
  
    node.right = TreeNode(val)  # ❌ Lost original right subtree!
    node.right.right = node.right  # This now points to the new node, not original

Correct Implementation:

if current_depth == depth - 1:
    # Save original references first
    original_left = node.left
    original_right = node.right
  
    # Then create new nodes with original subtrees
    node.left = TreeNode(val, original_left, None)
    node.right = TreeNode(val, None, original_right)

4. Not Returning After Insertion

Forgetting to return after performing the insertion at the target depth causes unnecessary traversal of deeper nodes and might lead to incorrect behavior if the tree has nodes at multiple levels.

Incorrect Implementation:

def dfs(node, current_depth):
    if node is None:
        return
  
    if current_depth == depth - 1:
        node.left = TreeNode(val, node.left, None)
        node.right = TreeNode(val, None, node.right)
        # Missing return statement! ❌
  
    # This will continue to traverse unnecessarily
    dfs(node.left, current_depth + 1)
    dfs(node.right, current_depth + 1)

Correct Implementation:

def dfs(node, current_depth):
    if node is None:
        return
  
    if current_depth == depth - 1:
        node.left = TreeNode(val, node.left, None)
        node.right = TreeNode(val, None, node.right)
        return  # ✅ Stop traversal after insertion
  
    dfs(node.left, current_depth + 1)
    dfs(node.right, current_depth + 1)

Prevention Strategy

To avoid these pitfalls:

  1. Always draw out the tree transformation on paper first, especially for edge cases
  2. Use descriptive variable names like original_left and original_right to make the code's intent clear
  3. Test with simple cases: a single node tree with depth = 1, and a two-level tree with depth = 2
  4. Trace through the recursion manually for small examples to verify the depth counting is correct
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings

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

Load More