We validate the binary search tree using a recursive helper validate(node, lower, upper) that enforces a
range invariant for every node, where node.val must be greater than lower and less than upper.
These bounds are inherited from ancestors and become tighter as we go down the tree, ensuring that each subtree also
maintains the BST property internally.
🔧 Invariant & Recurrence
At each call validate(node, lower, upper):
- Base case: if
node == null, the path is valid since there’s nothing left to violate the rules along this path. - Bounds check: return
falseifnode.val ≤ lowerornode.val ≥ upper— the current node breaks the allowed range. -
Recurse left: call
validate(node.left, lower, node.val)to ensure all values in the left subtree are greater than the inheritedlowerbound and less than the current node’s value. -
Recurse right: call
validate(node.right, node.val, upper)to ensure all values in the right subtree are greater than the current node’s value and less than the inheritedupperbound. -
Return the result of the left recursion and the result of the right recursion.
If both return
true, it confirms that every node in both subtrees satisfies the inherited bounds from ancestors and maintains the BST property internally. Since the current node’s value was already validated before these calls, it means the entire subtree rooted here also satisfies the inherited bounds from ancestors and maintains the BST property internally. Otherwise, a violation was found in one of the recursive branches.
💡 Why the Bounds Are Sufficient
When we make recursive calls to the left and right subtrees, we’ve already confirmed that the current node’s value lies strictly within
(lower, upper).
For the left subtree, every value must be less than the current node’s value, so we tighten the upper bound to node.val.
There’s no need to compare node.val with the inherited upper bound again — we already ensured that node.val < upper.
Similarly, for the right subtree, every value must be greater than the current node’s value, so we tighten the lower bound to node.val.
Again, there’s no need to compare node.val with the inherited lower bound — we already confirmed that node.val > lower.
⏱️ Complexity
Time: O(n) — each node is visited once.
Space: O(h) auxiliary — recursion stack height, where h is the tree height (worst case O(n) for a skewed tree; O(log n) for a balanced tree).
🧭 Result Semantics
If a call to validate(node, lower, upper) returns true, then the entire subtree rooted at
node satisfies both the BST property internally and all value constraints inherited from its ancestors.