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:
-
Tree depth counting: The root node is at depth 1, its children are at depth 2, and so on.
-
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
- Find all nodes at depth
-
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
- Create a new root node with value
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:
- We need to traverse the tree to find all nodes at a specific depth (
depth - 1
) - DFS allows us to track the current depth as we recursively traverse down the tree
- When we reach the target depth, we can perform the insertion operation
- 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
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:
- Create two new nodes with value
val
- Make these new nodes the immediate children of the current node
- 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 processedd
: The current depth in the tree
Algorithm Steps:
-
Base Case: If the current node is
None
, we return immediately (nothing to process). -
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
- This new node has value
- 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
- This new node has value
- Return after insertion (no need to go deeper)
- Create a new left child:
-
Recursive Traversal: If we haven't reached the target depth yet:
- Recursively call
dfs
on the left child withd + 1
- Recursively call
dfs
on the right child withd + 1
- Recursively call
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 EvaluatorExample 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 isdepth - 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.left → 2
1.right → 3
After insertion:
1.left → new_node_5 (left)
1.right → new_node_5 (right)
new_node_5(left).left → 2
new_node_5(right).right → 3
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:
- 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 beO(log n)
). This contributesO(h)
to the space complexity. - 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 create2^(d-1)
new nodes (one for each existing node at depthd-1
). This contributesO(m)
wherem = 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:
- Always draw out the tree transformation on paper first, especially for edge cases
- Use descriptive variable names like
original_left
andoriginal_right
to make the code's intent clear - Test with simple cases: a single node tree with
depth = 1
, and a two-level tree withdepth = 2
- Trace through the recursion manually for small examples to verify the depth counting is correct
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
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!