1325. Delete Leaves With a Given Value - Explanation

Problem Link

Description

You are given a binary tree root and an integer target, delete all the leaf nodes with value target.

Note that once you delete a leaf node with value target, if its parent node becomes a leaf node and has the value target, it should also be deleted (you need to continue doing that until you cannot).

Example 1:

Input: root = [1,2,3,5,2,2,5], target = 2

Output: [1,2,3,5,null,null,5]

Example 2:

Input: root = [3,null,3,3], target = 3

Output: []

Explanation: The output is an empty tree after removing all the nodes with value 3.

Constraints:

  • 1 <= number of nodes in the tree <= 3000
  • 1 <= Node.val, target <= 1000


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Tree Traversal - Understanding preorder, inorder, and postorder traversal patterns
  • Postorder Traversal - Processing children before parents, which is essential for bottom-up tree modifications
  • Recursion - Writing recursive functions that return modified subtrees to their parent

1. Recursion (Postorder Traversal)

Intuition

When we delete a leaf with the target value, its parent might become a new leaf. This means we need to process children before parents, which is exactly what postorder traversal does. By recursively processing left and right subtrees first, any newly exposed leaves are handled automatically when we return to the parent.

Algorithm

  1. If the root is null, return null.
  2. Recursively process the left subtree and update root.left with the result.
  3. Recursively process the right subtree and update root.right with the result.
  4. After processing children, check if the current node is now a leaf (both children null) and has the target value. If so, return null to delete it.
  5. Otherwise, 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 removeLeafNodes(self, root: Optional[TreeNode], target: int) -> Optional[TreeNode]:
        if not root:
            return None

        root.left = self.removeLeafNodes(root.left, target)
        root.right = self.removeLeafNodes(root.right, target)

        if not root.left and not root.right and root.val == target:
            return None

        return root

Time & Space Complexity

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

2. Iterative Postorder Traversal

Intuition

We can simulate postorder traversal using a stack and a set to track visited nodes. We also need to maintain parent pointers so that when we delete a leaf, we can update its parent's child reference. A node is processed only after both its children have been visited.

Algorithm

  1. Initialize a stack with the root, a set to track visited nodes, and a map for parent pointers.
  2. While the stack is not empty:
    • Pop a node from the stack.
    • If the node is a leaf with the target value, remove it by updating its parent's reference. If it has no parent, return null.
    • If the node has children and has not been visited, mark it as visited, push it back, then push its children (with parent mappings).
  3. Return the root after processing all nodes.
# 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 removeLeafNodes(self, root: Optional[TreeNode], target: int) -> Optional[TreeNode]:
        stack = [root]
        visit = set()
        parents = {root: None}

        while stack:
            node = stack.pop()
            if not node.left and not node.right:
                if node.val == target:
                    p = parents[node]
                    if not p:
                        return None
                    if p.left == node:
                        p.left = None
                    if p.right == node:
                        p.right = None
            elif node not in visit:
                visit.add(node)
                stack.append(node)
                if node.left:
                    stack.append(node.left)
                    parents[node.left] = node
                if node.right:
                    stack.append(node.right)
                    parents[node.right] = node

        return root

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

3. Iterative Postorder Traversal (Optimal)

Intuition

We can optimize the iterative approach by using a standard postorder traversal pattern without maintaining a separate parent map. The stack itself naturally tracks the parent: after processing a node, the next item on the stack is its parent. We use a visited pointer to avoid reprocessing the right subtree.

Algorithm

  1. Initialize a stack and a visited pointer to track the last processed node.
  2. Use two nested loops: the outer continues while the stack or current pointer is not empty.
  3. Inner loop: traverse left as far as possible, pushing nodes onto the stack.
  4. Peek the top node. If it has an unvisited right child, move to the right.
  5. Otherwise, pop the node. If it is a leaf with the target value, delete it by updating the parent's reference (the new top of the stack).
  6. If not deleted, mark it as visited.
  7. 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 removeLeafNodes(self, root: Optional[TreeNode], target: int) -> Optional[TreeNode]:
        if not root:
            return None

        stack = []
        cur = root
        visited = None

        while stack or cur:
            while cur:
                stack.append(cur)
                cur = cur.left

            cur = stack[-1]
            if cur.right and cur.right != visited:
                cur = cur.right
                continue

            stack.pop()
            if not cur.left and not cur.right and cur.val == target:
                if not stack:
                    return None

                parent = stack[-1]
                if parent.left == cur:
                    parent.left = None
                elif parent.right == cur:
                    parent.right = None
            else:
                visited = cur

            cur = None

        return root

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Common Pitfalls

Using Preorder Instead of Postorder

Deleting leaves must be done bottom-up. If you check a node before processing its children (preorder), you miss newly created leaves when children are deleted.

# Wrong: preorder - checks before children are processed
if not root.left and not root.right and root.val == target:
    return None
root.left = self.removeLeafNodes(root.left, target)
root.right = self.removeLeafNodes(root.right, target)

# Correct: postorder - process children first
root.left = self.removeLeafNodes(root.left, target)
root.right = self.removeLeafNodes(root.right, target)
if not root.left and not root.right and root.val == target:
    return None

Not Checking Both Conditions for Leaf Deletion

A node should only be deleted if it is both a leaf (no children) AND has the target value. Missing either condition causes incorrect deletions.

# Wrong: only checks value, deletes non-leaves
if root.val == target:
    return None

# Wrong: deletes all leaves regardless of value
if not root.left and not root.right:
    return None

# Correct: must be leaf AND have target value
if not root.left and not root.right and root.val == target:
    return None