title: Add One Row to Tree slug: add-one-row-to-tree difficulty: medium leetcode_id: 623 leetcode_url: https://leetcode.com/problems/add-one-row-to-tree/ categories: - trees - queue - recursion patterns: - slug: bfs is_optimal: true - slug: dfs is_optimal: false - slug: tree-traversal is_optimal: false function_signature: "def add_one_row(root: TreeNode, val: int, depth: int) -> TreeNode:" test_cases: visible: - input: { root: [4, 2, 6, 3, 1, 5], val: 1, depth: 2 } expected: [4, 1, 1, 2, null, null, 6, 3, 1, 5] - input: { root: [4, 2, null, 3, 1], val: 1, depth: 3 } expected: [4, 2, null, 1, 1, 3, null, null, 1] hidden: - input: { root: [1], val: 2, depth: 1 } expected: [2, 1] - input: { root: [1, 2, 3], val: 5, depth: 2 } expected: [1, 5, 5, 2, null, null, 3] - input: { root: [1], val: 2, depth: 2 } expected: [1, 2, 2] - input: { root: [4, 2, 6, 3, 1, 5], val: 1, depth: 1 } expected: [1, 4, null, 2, 6, 3, 1, 5] description: | Given the `root` of a binary tree and two integers `val` and `depth`, add a row of nodes with value `val` at the given depth `depth`. Note that the `root` node is at depth `1`. The adding rule is: - Given the integer `depth`, for each not null tree node `cur` at the depth `depth - 1`, create two tree nodes with value `val` as `cur`'s left subtree root and right subtree root. - `cur`'s original left subtree should be the left subtree of the new left subtree root. - `cur`'s original right subtree should be the right subtree of the new right subtree root. - If `depth == 1` that means there is no depth `depth - 1` at all, then create a tree node with value `val` as the new root of the whole original tree, and the original tree is the new root's left subtree. constraints: | - The number of nodes in the tree is in the range `[1, 10^4]` - The depth of the tree is in the range `[1, 10^4]` - `-100 <= Node.val <= 100` - `-10^5 <= val <= 10^5` - `1 <= depth <= the depth of tree + 1` examples: - input: "root = [4,2,6,3,1,5], val = 1, depth = 2" output: "[4,1,1,2,null,null,6,3,1,5]" explanation: "At depth 2, we insert two new nodes with value 1. The original left subtree (rooted at 2) becomes the left child of the new left node. The original right subtree (rooted at 6) becomes the right child of the new right node." - input: "root = [4,2,null,3,1], val = 1, depth = 3" output: "[4,2,null,1,1,3,null,null,1]" explanation: "At depth 3, we insert new nodes below the node with value 2. Its original children (3 and 1) become children of the newly inserted nodes." explanation: intuition: | Imagine you're inserting a new floor into an existing building. Every room on the floor above the insertion point needs to be "lifted up" and reconnected through the new floor. Think of it like this: you need to find all nodes at depth `depth - 1` (the "parent floor"). For each of these nodes, you insert two new nodes with value `val` between the parent and its children. The parent's original left child becomes the left child of the new left node, and similarly for the right side. The key insight is that this is a **level-based operation**. You need to reach a specific depth and perform surgery there. This naturally suggests either: - **BFS**: Traverse level by level until you reach depth `depth - 1`, then modify all nodes at that level - **DFS**: Track your current depth as you recurse, and perform the insertion when you reach the target The special case of `depth == 1` is elegant: since there's no "floor above" the root, you simply create a new root and make the entire original tree its left subtree. approach: | We solve this using **BFS with Level Tracking**: **Step 1: Handle the special case depth == 1** - If `depth == 1`, create a new root node with value `val` - Set the original tree as the new root's left child - Return the new root immediately   **Step 2: Use BFS to reach depth - 1** - Start with the root in a queue - Track the current depth, starting at `1` - Process level by level until we reach depth `depth - 1`   **Step 3: Insert new nodes at the target level** - For each node at depth `depth - 1`: - Save its current left and right children - Create a new left node with value `val`, connecting the original left child as its left child - Create a new right node with value `val`, connecting the original right child as its right child - Update the parent's left and right pointers to the new nodes   **Step 4: Return the modified tree** - Return the original root (or new root if depth was 1)   The BFS approach is intuitive because we naturally "stop" at the right level. DFS works equally well by passing the current depth through recursion. common_pitfalls: - title: Forgetting the depth == 1 Special Case description: | When `depth == 1`, there are no nodes at depth `0` to serve as parents. The problem explicitly states we should create a new root with the original tree as its left subtree. If you forget this case, your BFS/DFS will never find parents to attach to, or you might crash trying to access non-existent nodes. wrong_approach: "Assume depth is always >= 2" correct_approach: "Check depth == 1 first and create new root" - title: Losing Original Children References description: | When inserting new nodes, you must save the original children **before** overwriting the parent's pointers. If you write: ``` node.left = TreeNode(val) node.left.left = node.left # Oops, this is now the new node! ``` You've lost the reference to the original left subtree. Always save first: `old_left = node.left`, then rewire. wrong_approach: "Overwrite pointers before saving originals" correct_approach: "Save old_left and old_right before creating new nodes" - title: Off-by-One Depth Errors description: | The problem states the root is at depth `1`, not `0`. If you use 0-indexed depth, you'll insert the row at the wrong level. With root at depth 1: to insert at depth 2, you find nodes at depth 1 (the root) and add children to them. wrong_approach: "Treating root as depth 0" correct_approach: "Root is depth 1, find parents at depth - 1" - title: Only Inserting One New Node Per Parent description: | Each parent at depth `depth - 1` gets **two** new children (left and right), regardless of whether the parent originally had children there. Even if `node.left` was `None`, you still create `new_left = TreeNode(val)` and set `new_left.left = None`. The new node exists; it just has no children. wrong_approach: "Only insert where original children existed" correct_approach: "Always insert both left and right new nodes" key_takeaways: - "**Level-based tree modifications**: When operations target a specific depth, BFS level-by-level traversal is natural and intuitive" - "**Save before overwrite**: When rewiring tree pointers, always save original references before modifying parent pointers" - "**Special case the root**: Operations at depth 1 often require special handling since there's no parent above the root" - "**DFS with depth tracking**: Passing depth as a parameter in DFS is an effective alternative to BFS for depth-specific operations" time_complexity: "O(n). In the worst case (depth equals tree depth), we visit all nodes. BFS processes each node at most once." space_complexity: "O(w) where w is the maximum width of the tree. The queue holds at most one level of nodes. In a complete binary tree, this is O(n/2) = O(n)." solutions: - approach_name: BFS with Level Tracking is_optimal: true code: | from collections import deque class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def add_one_row(root: TreeNode | None, val: int, depth: int) -> TreeNode | None: # Special case: inserting at depth 1 means new root if depth == 1: new_root = TreeNode(val) new_root.left = root return new_root # BFS to find all nodes at depth - 1 queue = deque([root]) current_depth = 1 # Traverse until we reach the level just above target while current_depth < depth - 1: level_size = len(queue) for _ in range(level_size): node = queue.popleft() if node.left: queue.append(node.left) if node.right: queue.append(node.right) current_depth += 1 # Now queue contains all nodes at depth - 1 # Insert new nodes between each parent and its children while queue: node = queue.popleft() # Save original children old_left = node.left old_right = node.right # Create new nodes and rewire node.left = TreeNode(val) node.right = TreeNode(val) # Connect original children to new nodes node.left.left = old_left node.right.right = old_right return root explanation: | **Time Complexity:** O(n) — We may traverse all nodes to reach the target depth. **Space Complexity:** O(w) — Queue holds at most one level. Maximum width is n/2 for complete tree. BFS naturally processes level by level. We traverse until reaching depth - 1, then for each node at that level, we insert two new nodes with value `val`. The original subtrees become children of the new nodes. - approach_name: DFS with Depth Tracking is_optimal: true code: | class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def add_one_row(root: TreeNode | None, val: int, depth: int) -> TreeNode | None: # Special case: inserting at depth 1 means new root if depth == 1: new_root = TreeNode(val) new_root.left = root return new_root def dfs(node: TreeNode | None, current_depth: int) -> None: if not node: return # We're at depth - 1: insert new row here if current_depth == depth - 1: # Save original children old_left = node.left old_right = node.right # Create and wire new nodes node.left = TreeNode(val) node.right = TreeNode(val) node.left.left = old_left node.right.right = old_right return # No need to go deeper # Haven't reached target yet, keep going dfs(node.left, current_depth + 1) dfs(node.right, current_depth + 1) dfs(root, 1) return root explanation: | **Time Complexity:** O(n) — Visit nodes until reaching target depth. In worst case, visits all nodes. **Space Complexity:** O(h) — Recursion stack depth equals tree height. O(log n) for balanced, O(n) for skewed tree. DFS tracks depth via parameter. When reaching depth - 1, we perform the insertion. The recursion stops after insertion since we don't need to process deeper levels. Both BFS and DFS are optimal; DFS may use less space for wide trees, BFS for deep trees.