450. Delete Node in a BST - Explanation

Problem Link

Description

You are given a root node reference of a BST and a key, delete the node with the given key in the BST, if present. Return the root node reference (possibly updated) of the BST.

Basically, the deletion can be divided into two stages:

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

Note: There can be multiple results after deleting the node, return any one of them.

Example 1:

Input: root = [5,3,9,1,4], key = 3

Output: [5,4,9,1]

Explanation: Another valid answer is:

Example 2:

Input: root = [5,3,6,null,4,null,10,null,null,7], key = 3

Output: [5,4,6,null,null,null,10,7]

Constraints:

  • 0 <= The number of nodes in the tree <= 10,000.
  • -100,000 <= key, Node.val <= 100,000
  • All the values Node.val are unique.


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Search Tree Properties - Understanding that left children are smaller and right children are larger than the parent
  • Tree Traversal - Navigating BST nodes using the ordering property to find specific values
  • In-Order Successor/Predecessor - Finding the next or previous node in sorted order for node replacement during deletion

1. Recursion - I

Intuition

To delete a node in a BST, we first need to locate it using the BST property (left children are smaller, right children are larger). Once found, we handle three cases: if the node has no left child, replace it with its right child; if no right child, replace with the left child. The tricky case is when the node has both children. We find the in-order successor (the smallest node in the right subtree), copy its value to the current node, and then delete that successor node. This approach swaps values rather than restructuring pointers.

Algorithm

  1. If the root is null, return null.
  2. If the key is greater than the root's value, recursively delete from the right subtree.
  3. If the key is less than the root's value, recursively delete from the left subtree.
  4. If the key matches the root's value:
    • If there is no left child, return the right child.
    • If there is no right child, return the left child.
    • Otherwise, find the in-order successor (leftmost node in the right subtree), copy its value to the current node, and recursively delete the successor.
  5. Return the root.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if not root:
            return root

        if key > root.val:
            root.right = self.deleteNode(root.right, key)
        elif key < root.val:
            root.left = self.deleteNode(root.left, key)
        else:
            if not root.left:
                return root.right
            elif not root.right:
                return root.left

            cur = root.right
            while cur.left:
                cur = cur.left
            root.val = cur.val
            root.right = self.deleteNode(root.right, root.val)

        return root

Time & Space Complexity

  • Time complexity: O(h)O(h)
  • Space complexity: O(h)O(h) for the recursion stack.

Where hh is the height of the given binary search tree.


2. Recursion - II

Intuition

Instead of copying the successor's value and deleting it separately, we can restructure the tree directly. When the node to delete has both children, we find the in-order successor (leftmost node in the right subtree) and attach the left subtree of the deleted node as the left child of this successor. Then we simply return the right subtree as the replacement. This avoids value copying and removes the node in a single pass through the tree.

Algorithm

  1. If the root is null, return null.
  2. If the key is greater than the root's value, recursively delete from the right subtree.
  3. If the key is less than the root's value, recursively delete from the left subtree.
  4. If the key matches the root's value:
    • If there is no left child, return the right child.
    • If there is no right child, return the left child.
    • Otherwise, find the in-order successor (leftmost node in the right subtree), attach the deleted node's left subtree to this successor's left, delete the node, and return the right subtree.
  5. Return the root.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if not root:
            return root

        if key > root.val:
            root.right = self.deleteNode(root.right, key)
        elif key < root.val:
            root.left = self.deleteNode(root.left, key)
        else:
            if not root.left:
                return root.right
            elif not root.right:
                return root.left

            cur = root.right
            while cur.left:
                cur = cur.left
            cur.left = root.left
            res = root.right
            del root
            return res

        return root

Time & Space Complexity

  • Time complexity: O(h)O(h)
  • Space complexity: O(h)O(h) for the recursion stack.

Where hh is the height of the given binary search tree.


3. Iteration

Intuition

This approach avoids recursion by using a loop to find the node to delete and its parent. Once found, we handle the same three deletion cases, but we manually update parent pointers. When the node has two children, we find the in-order successor, detach it from its position, and replace the deleted node with it. This requires careful pointer manipulation to maintain tree structure.

Algorithm

  1. If the root is null, return null.
  2. Use a loop to find the node with the given key, tracking its parent.
  3. If the node is not found, return the original root.
  4. If the node has zero or one child:
    • Determine the child (left or right, whichever exists).
    • If deleting the root, return the child.
    • Otherwise, update the parent's pointer to point to the child.
  5. If the node has two children:
    • Find the in-order successor (leftmost node in the right subtree) and its parent.
    • If the successor is not the immediate right child, update its parent's left pointer to the successor's right child, then set the successor's right to the deleted node's right.
    • Set the successor's left to the deleted node's left.
    • Update the parent's pointer to the successor, or return the successor if deleting the root.
  6. Return the root.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if not root:
            return root

        parent = None
        cur = root

        # Find the node to delete
        while cur and cur.val != key:
            parent = cur
            if key > cur.val:
                cur = cur.right
            else:
                cur = cur.left

        if not cur:
            return root

        # Node with only one child or no child
        if not cur.left or not cur.right:
            child = cur.left if cur.left else cur.right
            if not parent:
                return child
            if parent.left == cur:
                parent.left = child
            else:
                parent.right = child
        else:
            # Node with two children
            par = None  # parent of right subTree min node
            delNode = cur
            cur = cur.right
            while cur.left:
                par = cur
                cur = cur.left

            if par:  # if there was a left traversal
                par.left = cur.right
                cur.right = delNode.right

            cur.left = delNode.left

            if not parent:  # if we're deleting root
                return cur

            if parent.left == delNode:
                parent.left = cur
            else:
                parent.right = cur

        return root

Time & Space Complexity

  • Time complexity: O(h)O(h)
  • Space complexity: O(1)O(1) extra space.

Where hh is the height of the given binary search tree.

Common Pitfalls

Confusing In-Order Successor with In-Order Predecessor

When a node has two children, you can replace it with either the in-order successor (smallest in right subtree) or in-order predecessor (largest in left subtree). Mixing these up or searching in the wrong subtree leads to an invalid BST.

# Wrong: Looking for successor in the LEFT subtree
cur = root.left
while cur.right:
    cur = cur.right  # This finds the predecessor, not successor

# Correct: Successor is the leftmost node in the RIGHT subtree
cur = root.right
while cur.left:
    cur = cur.left

Forgetting to Handle the Case When Key Is Not Found

If the key does not exist in the BST, the function should return the tree unchanged. Failing to handle this case can lead to incorrect behavior or null pointer exceptions.

# Wrong: Assumes key always exists
def deleteNode(self, root, key):
    # ... deletion logic without checking if key was found

# Correct: Return unchanged tree if key not found
def deleteNode(self, root, key):
    if not root:
        return root  # Key not found, return as-is
    # ... continue with deletion logic

Not Returning the Modified Subtree in Recursive Calls

Each recursive call modifies a subtree and returns its new root. Forgetting to assign the return value back to root.left or root.right means the deletion is not reflected in the tree.

# Wrong: Recursive call result is ignored
if key > root.val:
    self.deleteNode(root.right, key)  # Modification lost!

# Correct: Assign the result back to the parent's child pointer
if key > root.val:
    root.right = self.deleteNode(root.right, key)