LeetCode 1273 - Delete Tree Nodes
The problem provides a tree rooted at node 0, represented in two parallel arrays: parent and value. The parent[i] array
Difficulty: š” Medium
Topics: Array, Tree, Depth-First Search, Breadth-First Search
Solution
Problem Understanding
The problem provides a tree rooted at node 0, represented in two parallel arrays: parent and value. The parent[i] array indicates the parent of the i-th node, with -1 denoting the root. The value[i] array gives the integer value of each node. The task is to remove all subtrees whose sum of node values is zero and then return the count of the remaining nodes in the tree.
For example, if a subtree rooted at some node x sums to zero, all of its children and the node itself are removed. The process continues recursively up the tree, as removing a subtree may cause its parent to also sum to zero. The constraints guarantee that the tree has at least one node and the input represents a valid tree. Node values can be negative, zero, or positive.
Key edge cases include subtrees that sum to zero at the leaf level, subtrees including the root that sum to zero, and trees where no nodes need removal.
Approaches
A brute-force approach would recursively calculate the sum of every subtree for every node and remove subtrees whose sum is zero. While correct, this approach would require recomputing sums repeatedly and could take O(n²) time in the worst case for deep trees.
The optimal approach leverages a Depth-First Search (DFS) traversal. By visiting children first, we can calculate the sum of each subtree in a bottom-up manner. If a subtree's sum is zero, it can be removed immediately. This ensures each node is visited exactly once, giving a linear O(n) time solution. We maintain a tree structure (using a map from parent to children) to simplify traversal and removal.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(n) | Recomputes subtree sums repeatedly for each node |
| Optimal | O(n) | O(n) | DFS computes each subtree sum once and removes zero-sum subtrees |
Algorithm Walkthrough
- Build a tree representation using a dictionary (hash map) mapping each node to its children. This allows efficient access to all children of a node during traversal.
- Define a DFS function that recursively calculates the sum of a subtree rooted at a given node.
- For each node, recursively sum all values from its children using the DFS function. If a child's subtree sum is zero, skip counting its nodes (effectively removing the subtree).
- Add the current node's value to the sum of its children to get the total subtree sum for the current node.
- If the sum of the current node's subtree is zero, signal to the parent that this node should be removed.
- After DFS finishes, return the total number of nodes that are part of non-zero subtrees.
Why it works: The DFS traversal ensures a post-order processing of nodes, calculating subtree sums before evaluating the parent. This guarantees that any subtree whose sum is zero is fully accounted for and removed. Each node is processed exactly once, so the algorithm is correct and efficient.
Python Solution
from typing import List, Dict
class Solution:
def deleteTreeNodes(self, nodes: int, parent: List[int], value: List[int]) -> int:
tree: Dict[int, List[int]] = {i: [] for i in range(nodes)}
for i in range(1, nodes):
tree[parent[i]].append(i)
def dfs(node: int) -> int:
subtotal = value[node]
for child in tree[node]:
child_sum = dfs(child)
if child_sum == 0:
continue
subtotal += child_sum
return subtotal
total_sum = dfs(0)
def count_nodes(node: int) -> int:
subtotal = value[node]
count = 1
for child in tree[node]:
child_sum = dfs(child)
if child_sum == 0:
continue
count += count_nodes(child)
if subtotal + sum(dfs(c) for c in tree[node] if dfs(c) != 0) == 0:
return 0
return count
return count_nodes(0)
Explanation: We first build the adjacency list tree to represent children. The dfs function calculates subtree sums. The count_nodes function traverses the tree, counting nodes whose subtree sum is non-zero. Nodes with zero-sum subtrees are skipped. This implements the algorithm directly as discussed.
Go Solution
func deleteTreeNodes(nodes int, parent []int, value []int) int {
tree := make([][]int, nodes)
for i := 1; i < nodes; i++ {
tree[parent[i]] = append(tree[parent[i]], i)
}
var dfs func(int) int
dfs = func(node int) int {
subtotal := value[node]
for _, child := range tree[node] {
subtotal += dfs(child)
}
return subtotal
}
var countNodes func(int) int
countNodes = func(node int) int {
subtotal := value[node]
count := 1
for _, child := range tree[node] {
childSum := dfs(child)
if childSum == 0 {
continue
}
count += countNodes(child)
}
childSumTotal := 0
for _, child := range tree[node] {
cs := dfs(child)
if cs != 0 {
childSumTotal += cs
}
}
if subtotal+childSumTotal == 0 {
return 0
}
return count
}
return countNodes(0)
}
Explanation: The Go implementation mirrors Python, using slices instead of lists and recursive functions with closures. The adjacency list is built using make([][]int, nodes). Subtree sums are calculated with DFS, and nodes are counted if their subtree sum is non-zero.
Worked Examples
Example 1
Input: nodes = 7, parent = [-1,0,0,1,2,2,2], value = [1,-2,4,0,-2,-1,-1]
Tree representation:
0(1)
āā1(-2)
ā āā3(0)
āā2(4)
āā4(-2)
āā5(-1)
āā6(-1)
Step-by-step:
- DFS at leaf 3: sum = 0 ā remove
- DFS at leaf 4: sum = -2 ā keep
- DFS at leaf 5: sum = -1 ā keep
- DFS at leaf 6: sum = -1 ā keep
- DFS at node 2: 4 + (-2) + (-1) + (-1) = 0 ā remove
- DFS at node 1: -2 ā keep
- DFS at root 0: 1 + (-2) = -1 ā keep
Remaining nodes: 0 and 1 ā output 2.
Example 2
Input: nodes = 7, parent = [-1,0,0,1,2,2,2], value = [1,-2,4,0,-2,-1,-2]
Following the same procedure, only leaf 3 sums to zero and is removed. All other nodes remain ā output 6.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each node is visited once during DFS traversal. Building the tree is O(n). |
| Space | O(n) | Tree representation uses O(n) space. Recursion stack depth is at most O(n). |
The reasoning is that DFS computes subtree sums and counts nodes efficiently. Each node is visited exactly twice: once for sum calculation and once for counting remaining nodes. Space is dominated by the adjacency list and recursion stack.
Test Cases
# Provided examples
assert Solution().deleteTreeNodes(7, [-1,0,0,1,2,2,2], [1,-2,4,0,-2,-1,-1]) == 2 # Example 1
assert Solution().deleteTreeNodes(7, [-1,0,0,1,2,2,2], [1,-2,4,0,-2,-1,-2]) == 6 # Example 2
# Edge cases
assert Solution().deleteTreeNodes(1, [-1], [0]) == 0 # Single node, zero value
assert Solution().deleteTreeNodes(1, [-1], [5]) == 1 # Single node, non-zero value
assert Solution().deleteTreeNodes(3, [-1,0,0], [1,-1,0]) == 1 # Root with zero-sum children
# Larger tree
assert Solution().deleteTreeNodes(5, [-1,0,0,1,1], [1,-1,1,0,0]) == 3
| Test | Why |
|---|---|
| Single node, zero value | Ensures algorithm handles smallest tree |
| Single node, non-zero | Ensures correct count when root remains |
| Root with zero-sum children | Validates recursive removal |
| Larger tree | Tests correctness on non-trivial tree |
Edge Cases
Single Node Tree: If the tree contains only the root, its value alone determines whether it is removed. Implementation correctly returns 0 if root sum is zero, otherwise 1.
Leaf Nodes with Zero Value: Leaf