A clear explanation of pruning tree nodes where all root-to-leaf paths through them have sum less than a limit, using post-order DFS.
Problem Restatement
We are given the root of a binary tree and an integer limit.
A node is insufficient if every root-to-leaf path through that node has a sum strictly less than limit.
We need to delete all insufficient nodes and return the root of the resulting tree.
The official constraints state that the number of nodes is in [1, 5000] and -10^5 <= Node.val <= 10^5.
Input and Output
| Item | Meaning |
|---|---|
| Input | Root of a binary tree and integer limit |
| Output | Root after removing insufficient nodes |
Function shape:
def sufficientSubset(root: Optional[TreeNode], limit: int) -> Optional[TreeNode]:
...Examples
Example 1:
root = [1, 2, 3, 4, -99, -99, 7, 8, 9, -99, -99, 12, 13, -99, 14]
limit = 1Some paths sum to less than 1 and those nodes are removed.
Key Insight
Use DFS. For each node, we need to know the maximum path sum from that node to any leaf in its subtree.
A node is insufficient if the maximum path sum ending at a leaf below it (plus the current prefix from root) is still less than limit.
Equivalently: reduce limit by node.val as we descend. If a leaf has the remaining limit > 0, it is insufficient.
Algorithm
Define dfs(node, limit):
- If
nodeisNone, returnNone. - Recursively prune left and right subtrees with
limit - node.val. - If the node is a leaf after pruning and
limit > node.val, returnNone(insufficient). - Otherwise return
node.
Correctness
After pruning children, if a node has no children (leaf) and the current path sum from root through this node is less than limit, the node is removed.
If either child survives, the node is necessary (there is at least one sufficient path through it).
Edge Cases
- A missing child should not contribute a fake value to min/max or path computations.
- Leaf nodes often define the base case; verify the behavior on a single-node tree.
- When passing state down the tree, update it before visiting children that depend on the current node.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each node visited once |
| Space | O(h) | Recursion stack |
Common Pitfalls
- Do not optimize away the invariant; the code should still make it clear what condition is being maintained.
- Prefer problem-specific names over one-letter variables once the logic becomes stateful.
Implementation
from typing import Optional
class Solution:
def sufficientSubset(self, root: Optional[TreeNode], limit: int) -> Optional[TreeNode]:
if root is None:
return None
if root.left is None and root.right is None:
return None if root.val < limit else root
root.left = self.sufficientSubset(root.left, limit - root.val)
root.right = self.sufficientSubset(root.right, limit - root.val)
if root.left is None and root.right is None:
return None
return rootTesting
def run_tests():
s = Solution()
root = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(3)), TreeNode(3))
result = s.sufficientSubset(root, 4)
assert result is not None
root2 = TreeNode(5, TreeNode(-3), TreeNode(-2))
result2 = s.sufficientSubset(root2, 6)
print("all tests passed")
run_tests()| Test | Verification | Why |
|---|---|---|
| Root with children | Check structure | Some paths may be pruned |
| Root sum below limit | Entire tree may be pruned | All paths insufficient |