Taro Logo

Delete Node in a BST

Medium
Meta logo
Meta
1 view
Topics:
TreesRecursion

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:

  1. Search for a node to remove.
  2. If the node is found, delete the node.

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?

Solution


Delete Node in a BST

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.

1. Naive Approach (Brute Force)

The simplest approach involves:

  1. Search: Traverse the tree to find the node to be deleted.
  2. Deletion:
    • If the node is a leaf, simply remove it.
    • If the node has one child, replace the node with its child.
    • If the node has two children, find the inorder successor (minimum node in the right subtree) or inorder predecessor (maximum node in the left subtree), replace the node's value with the successor/predecessor's value, and then delete the successor/predecessor node.

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
  • Time Complexity: O(n) in the worst case (skewed tree), where n is the number of nodes.
  • Space Complexity: O(h) due to recursion stack, where h is the height of the tree. In the worst case, h can be n.

2. Optimized Approach

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
  • Time Complexity: O(h), where h is the height of the tree. In a balanced BST, h = O(log n). In the worst case (skewed tree), h = O(n).
  • Space Complexity: O(h) due to the recursion stack. In a balanced BST, this is O(log n). In the worst case, O(n).

3. Edge Cases

  • Empty Tree: If the root is None, return None.
  • Key Not Found: If the key is not in the tree, the original tree should be returned unchanged.
  • Deleting Root Node: The algorithm should correctly handle deleting the root node.
  • Node with No Children: Handles removal of leaf nodes.
  • Node with One Child: Handles cases where the node has only a left or right child.
  • Node with Two Children: Properly finds either the inorder successor or predecessor and maintains BST properties.

4. Summary

CategoryNaive ApproachOptimized Approach
Time ComplexityO(n)O(h)
Space ComplexityO(h)O(h)
Tree TypeAny BSTBalanced BST
ImplementationStraightforwardStraightforward

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).