Example 1
ready- Input
root = [ 2, 1, 3 ]- Expected
true- Why
- 1 < 2 < 3, so every subtree stays inside its bounds
A tree can look ordered one parent at a time and still hide a bad value three levels down. Your check has to protect the whole family tree, not just each immediate pair.
A binary search tree (BST) is a binary tree where every node obeys two strict rules:
Given root, return true when the entire tree follows those rules. Return false
otherwise.
A few ground rules:
null
marks a missing child.0 <= node count <= 10_000-2_147_483_648 <= node.val <= 2_147_483_647Hints
A node in the root's left subtree must be smaller than the root even when it isn't the root's child. What information could you carry down the tree to enforce that?
The root starts with no lower or upper limit. Moving left tightens the upper limit to the current value; moving right tightens the lower limit.
Reject a value that is equal to either limit. Open bounds encode the strict rule and catch duplicates without a special case.
Could you validate the tree while reading its values in sorted order, without carrying explicit lower and upper bounds?
Visible cases
root = [
2,
1,
3
]trueroot = [
5,
1,
4,
null,
null,
3,
6
]falseroot = [
2,
2,
2
]falseInterview signal
An in-order traversal visits a node's left subtree, then the node, then its right subtree. For a valid BST, that visit order must produce a strictly increasing sequence. Collect the sequence, then compare every neighboring pair.
def is_valid_bst(root: TreeNode | None) -> bool:
values: list[int] = []
stack: list[TreeNode] = []
current = root
while current is not None or stack:
while current is not None:
stack.append(current)
current = current.left
current = stack.pop()
values.append(current.val)
current = current.right
return all(values[i - 1] < values[i] for i in range(1, len(values)))The strict < matters. Using <= would quietly accept duplicate values.
Time O(n) — every node is visited once. Space O(n) for the collected values, plus the traversal stack.
You don't need to store the whole traversal. Give each recursive call an open bound: a limit the node must stay strictly above or below. When you move left, the current value becomes the new upper bound. When you move right, it becomes the new lower bound.
def is_valid_bst(root: TreeNode | None) -> bool:
def valid(node: TreeNode | None, low: int | None, high: int | None) -> bool:
if node is None:
return True
if low is not None and node.val <= low:
return False
if high is not None and node.val >= high:
return False
return (
valid(node.left, low, node.val)
and valid(node.right, node.val, high)
)
return valid(root, None, None)The bounds come from every ancestor on the path. That's why a value that looks fine
beside its parent can still be rejected for crossing the root's boundary. Using None
instead of made-up numeric sentinels also keeps both signed 32-bit extremes valid.
Time O(n) — each node is checked once. Space O(h) for the recursive stack, where
h is the tree's height.
Ready to run.
3 cases are queued.
Visible testcase
Case 1
Expected
true