1325. Delete Leaves With a Given Value - Explanation
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 <= 30001 <= Node.val, target <= 1000
Topics
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
- If the root is
null, returnnull. - Recursively process the left subtree and update
root.leftwith the result. - Recursively process the right subtree and update
root.rightwith the result. - After processing children, check if the current node is now a leaf (both children
null) and has the target value. If so, returnnullto delete it. - 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 rootTime & Space Complexity
- Time complexity:
- Space complexity: 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
- Initialize a stack with the root, a set to track visited nodes, and a map for parent pointers.
- 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).
- 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 rootTime & Space Complexity
- Time complexity:
- Space complexity:
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
- Initialize a stack and a
visitedpointer to track the last processed node. - Use two nested loops: the outer continues while the stack or current pointer is not empty.
- Inner loop: traverse left as far as possible, pushing nodes onto the stack.
- Peek the top node. If it has an unvisited right child, move to the right.
- 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).
- If not deleted, mark it as
visited. - 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 rootTime & Space Complexity
- Time complexity:
- Space complexity:
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 NoneNot 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
Sign in to join the discussion