
Problem Statement
In this problem, we are given a binary tree represented by its root node and a target integer. The task is to identify and delete all the leaf nodes in the binary tree that have a value equal to the target. A leaf node is characterized by having no children. However, the problem adds a dynamic aspect: after deleting all leaf nodes that initially match the target, it's possible that this deletion might cause other nodes to become leaf nodes. If these new leaf nodes also have the target value, they must also be deleted. This process should continue recursively until no further deletions can be made based on the specified conditions.
Examples
Example 1
Input:
root = [1,2,3,2,null,2,4], target = 2
Output:
[1,null,3,null,4]
Explanation:
Leaf nodes in green with value (target = 2) are removed (Picture in left). After removing, new nodes become leaf nodes with value (target = 2) (Picture in center).
Example 2
Input:
root = [1,3,3,3,2], target = 3
Output:
[1,3,null,null,2]
Example 3
Input:
root = [1,2,null,2,null,2], target = 2
Output:
[1]
Explanation:
Leaf nodes in green with value (target = 2) are removed at each step.
Constraints
- The number of nodes in the tree is in the range
[1, 3000]
. 1 <= Node.val, target <= 1000
Approach and Intuition
By analyzing the problem through the examples provided, we observe the following:
Recursive Deletion Mechanism: The deletion process relies on a recursive approach, where after deleting the current set of leaf nodes matching the target, the tree is re-evaluated to check if new leaf nodes with the target value have emerged.
Update Mechanism: Each node of the tree has to be checked whether it is a leaf node and if it matches the target value. Furthermore, if a leaf node is deleted, one must check its parent to see if it has now become a leaf node with the target as its value.
Traverse the Tree: A depth-first search (DFS) seems appropriate for this kind of tree modification. Start from the root and traverse downwards to the leaves.
Evaluate and Modify: When a leaf node (a node without children) is encountered, check if its value matches the target. If it matches, eliminate this node by setting the corresponding child link in the parent node to
null
.Recursive Check: After returning from the leaf to the parent, check if the parent has now become a leaf node due to the deletion of its child. If this newly-formed leaf node's value matches the target, it will also need to be deleted.
Iteration Until No Change: The entire process must be repeated until a full pass through the tree results in no further deletions.
The constraints given ensure that our recursive solution will be efficient and will not exceed memory limits, considering the tree could potentially have up to 3000 nodes and values are within a manageable range. This guarantees that the solution's complexity, while recursion-heavy, remains feasible for all input cases described.
Solutions
- C++
- Java
- Python
class Solution {
public:
TreeNode* deleteLeafNodes(TreeNode* root, int target) {
stack<TreeNode*> nodeStack;
TreeNode *node = root, *lastProcessedNode = nullptr;
while (!nodeStack.empty() || node != nullptr) {
while (node != nullptr) {
nodeStack.push(node);
node = node->left;
}
node = nodeStack.top();
if (node->right != lastProcessedNode && node->right) {
node = node->right;
continue;
}
nodeStack.pop();
if (node->right == nullptr && node->left == nullptr && node->val == target) {
if (nodeStack.empty()) {
return nullptr;
}
TreeNode* parentNode = nodeStack.top();
if (parentNode->left == node) {
parentNode->left = nullptr;
} else {
parentNode->right = nullptr;
}
}
lastProcessedNode = node;
node = nullptr;
}
return root;
}
};
This code snippet solves the problem of deleting leaf nodes in a binary tree if they have a specific value. It is written in C++ and utilizes tree traversal with the help of a stack to manage the nodes. Here's a summarized explanation of how the code functions:
- Define a TreeNode class with standard binary tree node attributes
left
,right
, andval
. - Use a stack called
nodeStack
to keep track of nodes during tree traversal. - Initialize pointers
node
to start at the root of the tree andlastProcessedNode
to help manage the traversal and track the node processing sequence. - Use a loop to traverse through the tree. Inner loops handle the navigation to the leftmost child by stacking each node, before checking and potentially moving right.
- After reaching a node and checking it does not have child nodes (indicative of a leaf), check the node's value against the target.
- If the node's value matches the target and it is a leaf, this node needs deletion:
- If the stack is empty after popping the node, it indicates the root itself was the target leaf, and the tree becomes empty (hence returns
nullptr
). - If not empty, determine whether the node was a left or a right child and set the corresponding parent pointer to
nullptr
, effectively deleting the node.
- If the stack is empty after popping the node, it indicates the root itself was the target leaf, and the tree becomes empty (hence returns
- Update
lastProcessedNode
to the node that was just processed and continue until all nodes are checked. - Return the modified tree's root. If all operations complete without finding a target leaf node, the original tree (root) is returned unchanged.
This solution provides an efficient means of selectively removing leaf nodes that match a given target value without affecting other nodes in the tree. The approach ensures that the tree's integrity is maintained even as specific nodes are removed.
class Solution {
public TreeNode pruneLeafNodes(TreeNode treeRoot, int removeVal) {
Stack<TreeNode> nodesStack = new Stack<>();
TreeNode activeNode = treeRoot, previousRightNode = null;
while (!nodesStack.isEmpty() || activeNode != null) {
while (activeNode != null) {
nodesStack.push(activeNode);
activeNode = activeNode.left;
}
activeNode = nodesStack.peek();
if (activeNode.right != previousRightNode && activeNode.right != null) {
activeNode = activeNode.right;
continue;
}
nodesStack.pop();
if (activeNode.right == null && activeNode.left == null && activeNode.val == removeVal) {
if (nodesStack.isEmpty()) {
return null;
}
TreeNode parentNode = nodesStack.isEmpty() ? null : nodesStack.peek();
if (parentNode != null) {
if (parentNode.left == activeNode) {
parentNode.left = null;
} else {
parentNode.right = null;
}
}
}
previousRightNode = activeNode;
activeNode = null;
}
return treeRoot;
}
}
The given Java solution outlines a method to delete leaf nodes in a binary tree if they hold a specific value. The procedure, implemented in the pruneLeafNodes
method of the Solution
class, utilizes a stack to manage tree traversal and node deletion without the need for recursion.
Here's how the provided solution handles the task:
- Initialize a stack to keep track of nodes as you traverse the tree.
nodesStack
is used for this purpose. - Use
activeNode
to track the current node starting fromtreeRoot
, andpreviousRightNode
to manage backtracking in the binary tree. - Enter a loop which continues as long as there are unvisited nodes, handling two major operations:
- Push left children of the current node to the stack until a leaf is reached.
- Check if the current node can proceed to the right or needs processing.
- On reaching a node without unprocessed right children, perform the leaf check and deletion:
- If a node is a leaf (
left
andright
children arenull
) and its value matches the targetremoveVal
, determine its relationship to the parent node (kept on top of the stack) and sever this connection, effectively deleting the node.
- If a node is a leaf (
- This process cleanses the tree of all leaves matching the target deletion value in a depth-first manner.
This approach ensures:
- Efficiency, as each node is processed exactly once.
- No use of additional data structures other than a stack, keeping memory overhead low.
- The tree's structure is only modified for specific leaf nodes, leaving other nodes intact.
Adopt this strategy in scenarios where selective deletion of leaf nodes based on their value is required, ensuring all nodes are processed in an orderly and efficient manner without recursion, which can be crucial for deep trees or systems with limited stack memory.
class Solution:
def pruneLeafNodes(self, tree_root: Optional[TreeNode], removal_val: int) -> Optional[TreeNode]:
if not tree_root:
return None
nodes_stack = []
current = tree_root
previously_visited = None
while nodes_stack or current:
# Navigate to leftmost node
while current:
nodes_stack.append(current)
current = current.left
# Peek the node at top of the stack without removing
current = nodes_stack[-1]
# Process right child if exists and not processed
if current.right and current.right is not previously_visited:
current = current.right
continue # Continue to leftmost of this subtree
# Ready to process this node, pop it
nodes_stack.pop()
# If node is a lead node and matches removal value
if not current.right and not current.left and current.val == removal_val:
if not nodes_stack:
return None # Entire tree is pruned
# Get parent node
parent_node = nodes_stack[-1] if nodes_stack else None
# Detach current node from its parent
if parent_node and parent_node.left is current:
parent_node.left = None
elif parent_node and parent_node.right is current:
parent_node.right = None
# Mark this node as visited
previously_visited = current
# Reset current to None to go back up stack
current = None
return tree_root # Returning possibly pruned tree root
This Python3 program utilizes a class called Solution
with the method pruneLeafNodes
to remove leaf nodes from a binary tree based on a specific value provided by the user (removal_val
). The method modifies the tree structure by strategically navigating through it with a stack, ensuring each node is visited once and process decisions are made at the right time, hence avoiding unnecessary recursion.
Here is a breakdown of how the method operates:
Initiate with a check to determine if the
tree_root
isNone
; if true, it will returnNone
, indicating an empty tree.Create a
nodes_stack
to manage the backtrack of nodes and two pointerscurrent
andpreviously_visited
initializing with thetree_root
andNone
, respectively.Employ a while loop that continues until all nodes are visited:
Utilize another inner loop to navigate to the leftmost node of the current node and push each traversed node into the stack.
Check if the current node has a right child that hasn't been visited; if true, shift the focus to the right child, effectively heading towards its leftmost subtree.
Process the current node:
- Only proceed if the current is a leaf node (has no left or right children) and its value equals
removal_val
. - If removing this node leaves the stack empty, return
None
, indicating no nodes left in the tree. - Identify the parent node; if current is the left child, detach it from parent's left and similarly for the right child.
- Only proceed if the current is a leaf node (has no left or right children) and its value equals
Mark the current node as visited to prevent reprocessing and reset the
current
node toNone
, which allows going up the stack to continue processing other nodes.
Finally, return
tree_root
, which may reflect the pruned tree structure, potentially altered if any leaf nodes were the specified value.
This method ensures efficient tree traversal and manipulation using a non-recursive approach, optimizing it for situations where tree recursion could be too deep or stack overflow might be a concern. It respects tree structure integrity at each step, handling each sub-tree only when it’s complete, making it optimal even for large trees.
No comments yet.