Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST.
Basically, the deletion can be divided into two stages:
Example 1:
Consider the following BST:
5
/ \
3 6
/ \ \
2 4 7
If the key to delete is 3, a valid output BST could be:
5
/ \
4 6
/ \
2 7
Another valid answer is:
5
/ \
2 6
\ \
4 7
Example 2:
Consider the BST:
5
/ \
3 6
/ \ \
2 4 7
If the key to delete is 0 (not present in the tree), the output should be the original tree:
5
/ \
3 6
/ \ \
2 4 7
Example 3:
If the input is an empty tree, and the key to delete is 0, the output should be an empty tree.
Write a function to delete a node from a BST given its root and a key. The function should return the root of the modified BST.
What are the time and space complexities of your solution? How does it behave in different edge cases?
This problem involves deleting a node with a given key from a Binary Search Tree (BST) while maintaining the BST properties. Let's explore both a naive and an optimized approach.
The simplest approach involves:
Code Example (Python):
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def deleteNode(root, key):
if not root:
return None
if key < root.val:
root.left = deleteNode(root.left, key)
elif key > root.val:
root.right = deleteNode(root.right, key)
else:
# Node found
if not root.left:
return root.right
elif not root.right:
return root.left
# Node with two children: Get the inorder successor
min_node = root.right
while min_node.left:
min_node = min_node.left
root.val = min_node.val
root.right = deleteNode(root.right, root.val)
return root
The optimized approach aims to maintain the O(height) complexity by ensuring that the tree traversal is proportional to the height of the tree.
The algorithm remains the same as the naive approach, but the key is to ensure that in a balanced BST, the height remains logarithmic with the number of nodes, i.e., O(log n).
Code Example (Python):
The code remains the same as above, but the performance characteristics change if the tree is balanced.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def deleteNode(root, key):
if not root:
return None
if key < root.val:
root.left = deleteNode(root.left, key)
elif key > root.val:
root.right = deleteNode(root.right, key)
else:
# Node found
if not root.left:
return root.right
elif not root.right:
return root.left
# Node with two children: Get the inorder successor
min_node = root.right
while min_node.left:
min_node = min_node.left
root.val = min_node.val
root.right = deleteNode(root.right, root.val)
return root
Category | Naive Approach | Optimized Approach |
---|---|---|
Time Complexity | O(n) | O(h) |
Space Complexity | O(h) | O(h) |
Tree Type | Any BST | Balanced BST |
Implementation | Straightforward | Straightforward |
The optimal solution offers a time complexity of O(h), making it more efficient for balanced BSTs. However, in the worst-case scenario (skewed tree), both approaches degrade to O(n).