Files
codetutor/backend/data/questions/add-one-row-to-tree.yaml

263 lines
12 KiB
YAML

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
&nbsp;
**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`
&nbsp;
**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
&nbsp;
**Step 4: Return the modified tree**
- Return the original root (or new root if depth was 1)
&nbsp;
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.