Files
codetutor/backend/data/questions/insert-into-a-binary-search-tree.yaml

206 lines
9.0 KiB
YAML

title: Insert into a Binary Search Tree
slug: insert-into-a-binary-search-tree
difficulty: easy
leetcode_id: 701
leetcode_url: https://leetcode.com/problems/insert-into-a-binary-search-tree/
categories:
- trees
patterns:
- slug: tree-traversal
is_optimal: true
function_signature: "def insert_into_bst(root: TreeNode, val: int) -> TreeNode:"
test_cases:
visible:
- input: { root: [4, 2, 7, 1, 3], val: 5 }
expected: [4, 2, 7, 1, 3, 5]
- input: { root: [40, 20, 60, 10, 30, 50, 70], val: 25 }
expected: [40, 20, 60, 10, 30, 50, 70, null, null, 25]
hidden:
- input: { root: [], val: 5 }
expected: [5]
- input: { root: [1], val: 2 }
expected: [1, null, 2]
- input: { root: [2], val: 1 }
expected: [2, 1]
- input: { root: [5, 3, 7], val: 2 }
expected: [5, 3, 7, 2]
- input: { root: [5, 3, 7], val: 8 }
expected: [5, 3, 7, null, null, null, 8]
- input: { root: [10, 5, 15, 3, 7], val: 6 }
expected: [10, 5, 15, 3, 7, null, null, null, null, 6]
description: |
You are given the `root` node of a binary search tree (BST) and a `val` to insert into the tree. Return *the root node of the BST after the insertion*. It is **guaranteed** that the new value does not exist in the original BST.
**Notice** that there may exist multiple valid ways for the insertion, as long as the tree remains a BST after insertion. You can return **any of them**.
constraints: |
- `0 <= Number of nodes <= 10^4`
- `-10^8 <= Node.val <= 10^8`
- All values `Node.val` are **unique**
- `-10^8 <= val <= 10^8`
- It's **guaranteed** that `val` does not exist in the original BST
examples:
- input: "root = [4,2,7,1,3], val = 5"
output: "[4,2,7,1,3,5]"
explanation: "Insert 5 as the left child of 7, since 5 < 7 and 7 has no left child. Another valid answer would insert 5 elsewhere while maintaining BST properties."
- input: "root = [40,20,60,10,30,50,70], val = 25"
output: "[40,20,60,10,30,50,70,null,null,25]"
explanation: "Navigate right from 20 (since 25 > 20), then left from 30 (since 25 < 30). Insert 25 as the left child of 30."
- input: "root = [], val = 5"
output: "[5]"
explanation: "When the tree is empty, the new value becomes the root."
explanation:
intuition: |
Think of a BST as a decision tree for binary search. At each node, you ask: "Is my value smaller or larger than this node?" The answer tells you which direction to go — left for smaller, right for larger.
The key insight is that **every value has exactly one "correct" leaf position** where it can be inserted without restructuring the tree. You simply follow the BST property until you hit an empty spot (`None`), and that's where the new node belongs.
Imagine you're looking up a word in a dictionary. You flip to the middle, decide if your word comes before or after, and keep narrowing down. When you reach the exact spot where your word *would* be if it existed, that's where you insert it.
This means insertion is essentially a **search that ends at a null pointer**, and we replace that null with our new node.
approach: |
We solve this using a **Recursive BST Traversal**:
**Step 1: Handle the base case**
- If `root` is `None`, we've found the insertion point
- Create and return a new `TreeNode` with the given value
&nbsp;
**Step 2: Decide which subtree to explore**
- If `val < root.val`, the new node belongs in the **left subtree**
- If `val > root.val`, the new node belongs in the **right subtree**
&nbsp;
**Step 3: Recursively insert and connect**
- Make a recursive call on the appropriate child
- Assign the result back to `root.left` or `root.right`
- This automatically handles the case where the child was `None`
&nbsp;
**Step 4: Return the root**
- Return the (unchanged) root to maintain the tree structure
- The recursive assignment ensures the new node gets properly linked
&nbsp;
The recursion naturally terminates when we reach a `None` child, which becomes the insertion point. By assigning the recursive result back to the parent's child pointer, we elegantly connect the new node.
common_pitfalls:
- title: Forgetting to Handle the Empty Tree
description: |
When `root` is `None` (empty tree), you must return a new node as the root. Some solutions only handle the case of inserting into existing trees, causing a crash or returning `None` for empty input.
Always check for `root is None` as your base case and return the new node.
wrong_approach: "Only handling non-empty trees"
correct_approach: "Check if root is None and return new TreeNode(val)"
- title: Not Connecting the New Node
description: |
A common mistake is to traverse to the correct position but forget to actually link the new node to its parent. Simply creating a new node isn't enough — you must assign it to the parent's `left` or `right` pointer.
The recursive approach handles this elegantly by assigning `root.left = insertIntoBST(root.left, val)`.
wrong_approach: "Creating node without linking to parent"
correct_approach: "Assign recursive result back to parent's child pointer"
- title: Modifying Existing Node Values
description: |
BST insertion adds a **new node**, not modifying an existing one. Don't try to swap values or restructure the tree — simply find the correct empty spot and insert there.
The problem guarantees the value doesn't exist, so you'll always reach a `None` position.
wrong_approach: "Changing existing node values"
correct_approach: "Always insert as a new leaf node"
key_takeaways:
- "**BST property drives insertion**: Left for smaller, right for larger — no complex logic needed"
- "**Recursion simplifies tree operations**: The base case handles insertion, recursive calls handle navigation"
- "**Insertion is search + create**: Follow the search path until hitting `None`, then insert"
- "**Foundation for BST operations**: This pattern extends to deletion, search, and validation problems"
time_complexity: "O(h) where h is the height of the tree. In a balanced BST, h = log(n), giving O(log n). In the worst case (skewed tree), h = n, giving O(n)."
space_complexity: "O(h) for the recursion stack. In a balanced tree this is O(log n), worst case O(n) for a skewed tree. The iterative approach achieves O(1) space."
solutions:
- approach_name: Recursive
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 insert_into_bst(root: TreeNode | None, val: int) -> TreeNode:
# Base case: found the insertion point
if root is None:
return TreeNode(val)
# Decide which subtree to insert into
if val < root.val:
# Value belongs in left subtree
root.left = insert_into_bst(root.left, val)
else:
# Value belongs in right subtree
root.right = insert_into_bst(root.right, val)
# Return root to maintain tree structure
return root
explanation: |
**Time Complexity:** O(h) — We traverse from root to a leaf, where h is the tree height.
**Space Complexity:** O(h) — Recursion stack depth equals the path length.
The recursive approach elegantly handles both navigation and connection. When we hit `None`, we return the new node, and the parent's assignment (`root.left = ...`) automatically links it.
- approach_name: Iterative
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 insert_into_bst(root: TreeNode | None, val: int) -> TreeNode:
# Handle empty tree
if root is None:
return TreeNode(val)
# Find the correct position
current = root
while True:
if val < current.val:
# Go left
if current.left is None:
# Found insertion point
current.left = TreeNode(val)
break
current = current.left
else:
# Go right
if current.right is None:
# Found insertion point
current.right = TreeNode(val)
break
current = current.right
return root
explanation: |
**Time Complexity:** O(h) — Same traversal as recursive approach.
**Space Complexity:** O(1) — No recursion stack, only a single pointer.
The iterative approach uses a while loop to find the insertion point, then directly links the new node. This avoids recursion overhead and is slightly more efficient in practice, though both have the same time complexity.