We have a binary tree and a list of node values to delete. When we delete a node, it disappears from the tree, and its children (if any) become roots of new independent trees. The original root is also a tree in the forest, unless it gets deleted too.
The problem combines two tasks: removing nodes and collecting the roots of the resulting subtrees. A node becomes a new root when its parent gets deleted (or when it was the original root and it survives). A node stops being a root if it itself gets deleted.
The two tasks are coupled. Deleting a node is what creates new roots (its children), so deletion and root collection have to be tracked together during the traversal rather than handled in separate phases.
Number of nodes <= 1000 -> The tree is small. Even O(n^2) runs instantly. The focus is on getting the logic right, not on performance.Each node has a distinct value between 1 and 1000 -> Distinct values mean we can use a HashSet for O(1) deletion lookups. No ambiguity about which node to delete.to_delete.length <= 1000 -> The delete list can be as large as the tree itself (we might delete every node). We need to handle the case where nothing survives.Traverse the tree, find every node that needs deleting, and handle the consequences at each one: disconnect it from its parent and promote its surviving children to new roots.
A BFS (level-order traversal) processes nodes one by one, but it reaches a node without remembering who its parent was, and the disconnect happens at the parent. So we first build a parent map, then run a second BFS that performs the deletions. Each node decides its own fate independently: if a deleted node's child is also marked for deletion, the child is not promoted here; its own surviving children get promoted when the BFS reaches that child.
The bookkeeping adds up: a parent map, a set of values to delete, and the original root handled as a special case at the end.
to_delete, and d <= n.The two passes and the parent map exist because BFS reaches a node before its children, so a deleted node has to reach back up to its parent to sever the link. A traversal that resolves children before finalizing the parent's pointers removes both costs, which is the next approach.
A recursive DFS can sever links without any parent map by using its return value: each call returns what the parent's pointer should become, the node itself if it survives or null if it is deleted. The parent assigns node.left = dfs(node.left, ...) after the recursive call finishes, so every severed link is updated exactly where it is stored, in the parent. This is the post-order part: a node's child pointers are finalized only after both subtrees have been resolved.
Root status flows in the opposite direction, downward. A node is a root of the forest when its parent was deleted (or when it is the original root), so the current node's "I am being deleted" decision becomes the child's isRoot flag in the recursive call. On entering a node, if isRoot is true and the node survives, it goes into the forest.
One DFS therefore deletes nodes, promotes children, and collects roots, with no parent map and no second traversal.
A surviving root is added to the forest on entry, before its subtree has been cleaned of deleted descendants. This is safe because the forest stores a reference to the node, not a copy. The recursive calls that follow prune deleted descendants in place, so by the time the top-level call returns, every tree reachable from the forest list is in its final shape.
to_delete into a HashSet for O(1) lookup.isRoot flag.isRoot = true). Otherwise pass isRoot = false.isRoot = true.to_delete.