701. Insert into a Binary Search Tree
Problem Description
You are given the root node of a binary search tree (BST) and a value to insert into the tree. Your task is to insert the new value into the BST and return the root node of the modified tree.
The problem guarantees that the value you're inserting does not already exist in the BST.
A binary search tree follows these properties:
- For any node, all values in its left subtree are less than the node's value
- For any node, all values in its right subtree are greater than the node's value
There may be multiple valid positions where you can insert the new value while maintaining the BST properties. You can return any valid result.
For example, if you have a BST with root value 4
, left child 2
, and right child 7
, and you need to insert value 5
, you could insert it as the left child of 7
. The tree would still maintain its BST properties since 5
is greater than 4
but less than 7
.
The solution uses a recursive approach:
- If we reach a null position (empty spot), we create a new node with the given value
- If the current node's value is greater than the value to insert, we go to the left subtree
- If the current node's value is less than the value to insert, we go to the right subtree
- We continue this process until we find an appropriate empty position for the new node
Intuition
The key insight comes from understanding how we search for values in a BST. When looking for where to place a new value, we follow the same path we would take if we were searching for that value.
Think about it this way: if you're trying to insert value 5
into a BST, you would compare it with each node you encounter. If 5
is less than the current node, you go left; if it's greater, you go right. Eventually, you'll reach a point where there's no more nodes to compare with - that's exactly where the new value belongs.
This naturally leads to a recursive approach. At each node, we make a decision:
- If we've reached an empty spot (
None
), we've found where to insert our new node - Otherwise, we compare the value with the current node and decide which direction to go
The beauty of this approach is that we don't need to restructure the existing tree. We simply traverse down to find an empty leaf position and attach the new node there. This guarantees the BST property is maintained because we're following the same comparison logic that defines a BST.
Why does recursion work so well here? Because the problem has a recursive structure - a BST is essentially a root node with two smaller BSTs as children. When we insert into the left or right subtree, we're solving the same problem on a smaller scale. The base case is when we hit a None
node, where we simply create and return the new node.
The recursive calls also handle the linking automatically. When we call root.left = self.insertIntoBST(root.left, val)
, we're either keeping the existing left child (if insertion happens deeper in the tree) or replacing None
with the new node (if we've reached the insertion point).
Learn more about Tree, Binary Search Tree and Binary Tree patterns.
Solution Approach
The solution uses a recursive approach to traverse the BST and find the correct position for the new value.
Let's walk through the implementation step by step:
Base Case:
if root is None: return TreeNode(val)
When we encounter a None
node (empty position), we've found where to insert our new value. We create a new TreeNode
with the given value and return it. This becomes a leaf node in the BST.
Recursive Cases:
if root.val > val: root.left = self.insertIntoBST(root.left, val) else: root.right = self.insertIntoBST(root.right, val)
Here we compare the value to insert with the current node's value:
- If
root.val > val
, the new value should go in the left subtree (BST property: smaller values go left) - Otherwise (
root.val < val
), the new value should go in the right subtree (BST property: larger values go right)
The key insight is that we recursively call insertIntoBST
on the appropriate subtree and assign the result back to either root.left
or root.right
. This serves two purposes:
- If the insertion happens deeper in the tree, it maintains the existing connection
- If the child is
None
, it connects the newly created node to the parent
Return Statement:
return root
After the insertion is complete, we return the root of the tree. This is crucial for maintaining connections as we backtrack through the recursion.
The algorithm follows this pattern:
- Start at the root and compare values
- Navigate left or right based on the comparison
- Continue until finding a
None
position - Create the new node at that position
- Backtrack through recursion, maintaining all parent-child connections
Time Complexity: O(h)
where h
is the height of the tree. In the worst case (skewed tree), this could be O(n)
.
Space Complexity: O(h)
for the recursion call stack.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through inserting value 5
into this BST:
Initial BST: 4 / \ 2 7 / 1
Step 1: Start at root (4)
- Compare: 5 > 4, so we need to go right
- Recursive call:
insertIntoBST(node_7, 5)
Step 2: At node 7
- Compare: 5 < 7, so we need to go left
- Check: node_7.left is
None
(empty spot found!) - Recursive call:
insertIntoBST(None, 5)
Step 3: At None (base case)
- Create new TreeNode(5)
- Return this new node
Step 4: Backtrack to node 7
- Assign: node_7.left = TreeNode(5)
- Return node_7
Step 5: Backtrack to root (4)
- Assign: root.right = node_7 (maintains existing connection)
- Return root
Final BST:
4
/ \
2 7
/ /
1 5
The key insight is how the recursive returns maintain the tree structure:
- When we hit
None
, we create and return the new node - Each recursive level assigns the returned value to either left or right child
- Existing nodes simply return themselves, preserving the original structure
- Only the
None
position gets replaced with the new node
This ensures the BST property is maintained: 1 < 2 < 4 < 5 < 7, and each node's left children are smaller while right children are larger.
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 insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
13 """
14 Insert a value into a Binary Search Tree (BST).
15
16 Args:
17 root: The root node of the BST
18 val: The value to be inserted
19
20 Returns:
21 The root of the modified BST with the new value inserted
22 """
23 # Base case: if we reach an empty position, create a new node
24 if root is None:
25 return TreeNode(val)
26
27 # BST property: smaller values go to the left subtree
28 if root.val > val:
29 # Recursively insert into the left subtree
30 root.left = self.insertIntoBST(root.left, val)
31 else:
32 # BST property: larger or equal values go to the right subtree
33 # Recursively insert into the right subtree
34 root.right = self.insertIntoBST(root.right, val)
35
36 # Return the root of the modified tree
37 return root
38
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 /**
18 * Inserts a new value into a Binary Search Tree (BST).
19 * Maintains BST property: left subtree values < root < right subtree values.
20 *
21 * @param root The root node of the BST
22 * @param val The value to be inserted into the BST
23 * @return The root of the modified BST with the new value inserted
24 */
25 public TreeNode insertIntoBST(TreeNode root, int val) {
26 // Base case: if we reach a null position, create and return a new node
27 if (root == null) {
28 return new TreeNode(val);
29 }
30
31 // If the value to insert is less than current node's value,
32 // recursively insert into the left subtree
33 if (root.val > val) {
34 root.left = insertIntoBST(root.left, val);
35 }
36 // If the value to insert is greater than or equal to current node's value,
37 // recursively insert into the right subtree
38 else {
39 root.right = insertIntoBST(root.right, val);
40 }
41
42 // Return the unchanged root node after insertion
43 return root;
44 }
45}
46
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 {
13public:
14 /**
15 * Inserts a value into a Binary Search Tree (BST)
16 * @param root - The root node of the BST
17 * @param val - The value to be inserted into the BST
18 * @return The root of the modified BST
19 */
20 TreeNode* insertIntoBST(TreeNode* root, int val) {
21 // Base case: If we reach a null position, create and return a new node
22 if (root == nullptr) {
23 return new TreeNode(val);
24 }
25
26 // If the value to insert is less than current node's value,
27 // recursively insert into the left subtree
28 if (val < root->val) {
29 root->left = insertIntoBST(root->left, val);
30 }
31 // If the value to insert is greater than or equal to current node's value,
32 // recursively insert into the right subtree
33 else {
34 root->right = insertIntoBST(root->right, val);
35 }
36
37 // Return the unchanged root node
38 return root;
39 }
40};
41
1/**
2 * Definition for a binary tree node.
3 * Each node contains a value and references to left and right child nodes
4 */
5class TreeNode {
6 val: number;
7 left: TreeNode | null;
8 right: TreeNode | null;
9
10 constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
11 this.val = (val === undefined ? 0 : val);
12 this.left = (left === undefined ? null : left);
13 this.right = (right === undefined ? null : right);
14 }
15}
16
17/**
18 * Inserts a new value into a Binary Search Tree (BST)
19 * @param root - The root node of the BST (can be null for empty tree)
20 * @param val - The value to be inserted into the BST
21 * @returns The root of the modified BST with the new value inserted
22 *
23 * Time Complexity: O(h) where h is the height of the tree
24 * Space Complexity: O(h) due to recursive call stack
25 */
26function insertIntoBST(root: TreeNode | null, val: number): TreeNode | null {
27 // Base case: if the current position is empty, create a new node
28 if (!root) {
29 return new TreeNode(val);
30 }
31
32 // BST property: values less than current node go to the left subtree
33 if (root.val > val) {
34 // Recursively insert into the left subtree
35 root.left = insertIntoBST(root.left, val);
36 } else {
37 // BST property: values greater than or equal to current node go to the right subtree
38 // Recursively insert into the right subtree
39 root.right = insertIntoBST(root.right, val);
40 }
41
42 // Return the unchanged root node
43 return root;
44}
45
Time and Space Complexity
The time complexity is O(h)
where h
is the height of the BST. In the worst case when the tree is skewed (essentially a linked list), h = n
, making the time complexity O(n)
. In the best case of a balanced BST, h = log(n)
, making the time complexity O(log n)
. The algorithm traverses from root to a leaf position where the new node should be inserted, visiting at most h
nodes.
The space complexity is O(h)
due to the recursive call stack. Similar to time complexity, in the worst case of a skewed tree, the recursion depth reaches n
, making the space complexity O(n)
. In the best case of a balanced tree, the recursion depth is log(n)
, making the space complexity O(log n)
. Each recursive call uses constant extra space, but the call stack accumulates up to h
function calls before returning.
The reference answer stating O(n)
for both complexities represents the worst-case scenario where the BST degenerates into a linear structure with height equal to the number of nodes n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Assign the Recursive Call Result
A common mistake is calling the recursive function without assigning its return value back to the parent's child pointer:
Incorrect Code:
if root.val > val: self.insertIntoBST(root.left, val) # Missing assignment! else: self.insertIntoBST(root.right, val) # Missing assignment! return root
Why This Fails:
When root.left
or root.right
is None
, the recursive call creates a new node, but without assignment, this new node never gets connected to the parent. The tree remains unchanged, and the value is never actually inserted.
Correct Approach:
if root.val > val: root.left = self.insertIntoBST(root.left, val) # Properly assigns the result else: root.right = self.insertIntoBST(root.right, val) # Properly assigns the result return root
2. Handling Edge Case of Empty Tree Incorrectly
Another pitfall is not handling the case when the initial root is None
:
Incorrect Code:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if root.val > val: # This will crash if root is None!
if root.left is None:
root.left = TreeNode(val)
else:
self.insertIntoBST(root.left, val)
# ... rest of code
Why This Fails:
If the tree is initially empty (root
is None
), accessing root.val
will raise an AttributeError
.
Correct Approach:
Always check if the root is None
first:
if root is None: return TreeNode(val) # Then proceed with comparisons
3. Using Iterative Approach Without Proper Parent Tracking
When attempting an iterative solution, a common mistake is losing track of the parent node:
Incorrect Iterative Attempt:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root:
return TreeNode(val)
current = root
while current:
if val < current.val:
current = current.left # We lose the parent reference!
else:
current = current.right
current = TreeNode(val) # This doesn't attach to the tree!
return root
Correct Iterative Approach:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root:
return TreeNode(val)
current = root
while True:
if val < current.val:
if current.left is None:
current.left = TreeNode(val)
break
current = current.left
else:
if current.right is None:
current.right = TreeNode(val)
break
current = current.right
return root
The recursive approach naturally handles parent-child connections through the call stack, making it less error-prone for this problem.
What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.
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
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!