A common first thought is to directly translate the BST definition into a simple recursive check. The definition states that a node’s value must be greater than its left child’s value and smaller than its right child’s value. This leads to an intuitive check at each node.
For each node in the tree, we recursively check if node.val is greater than node.left.val and less than node.right.val. While this seems correct, it’s a flawed approach. It only validates a node against its immediate children, failing to enforce the constraint against all ancestor nodes. For example, a node might be a valid child of its parent but still be in the wrong place relative to its grandparent, violating the overall BST structure.
Here is the test case which shows it fails, where 4 should be on the left of node root node 5.
booleanisValidBST(TreeNode root) {
if (root ==null) {
returntrue;
}
// false if left is > than rootif (root.left!=null&& root.left.val> root.val) {
returnfalse;
}
// false if right is<than rootif (root.right!=null&& root.right.val<root.val) {
returnfalse;
}
// false if, recursively, the left or right is not a BSTif (!isValidBST(root.left) ||!isValidBST(root.right)) {
returnfalse;
}
// passing all that, it's a BSTreturntrue;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classSolution:
defisValidBST(self, root: Optional[TreeNode]) -> bool:
ifnot root:
returnTrue# Flawed check: false if left child is > than rootif root.left and root.left.val > root.val:
returnFalse# Flawed check: false if right child is < than rootif root.right and root.right.val < root.val:
returnFalse# Recursively apply the same flawed logicifnot self.isValidBST(root.left) ornot self.isValidBST(root.right):
returnFalsereturnTrue
Method 1 - Recursive but Inefficient - Max Value and Min Value for Each Node#
To fix the flaw in the first approach, we need to ensure that for any given node, all nodes in its left subtree are smaller and all nodes in its right subtree are larger. This suggests a more thorough validation at each step.
For each node, check if max value in left subtree is smaller than the node and min value in right subtree greater than the node.
For each node n in the tree, we can perform two separate traversals:
Traverse the entire left subtree of n to confirm every node’s value is less than n.val.
Traverse the entire right subtree of n to confirm every node’s value is greater than n.val.
We do this for every single node in the main tree. This is guaranteed to be correct because it rigorously checks the global BST property for each node.
classSolution {
publicintisValidBST(TreeNode root) {
if (root ==null)
returntrue;
/* false if the max of the left is > than us */if (root.left!=null&& maxValue(root.left) > root.val) {
returnfalse;
}
/* false if the min of the right is <= than us */if (root.right!=null&& minValue(root.right) < root.val) {
returnfalse;
}
/* false if, recursively, the left or right is not a BST */if (!isValidBST(root.left) ||!isValidBST(root.right)) {
returnfalse;
}
/* passing all that, it's a BST */returntrue;
}
// It is assumed that you have helper functions `minValue()` and `maxValue()` that return the min or max int value from a non-empty tree.privateintminValue(TreeNode root) {
TreeNode current = root; // loop down to find the leftmost leafwhile (current.left!=null) {
current = current.left;
}
return (current.val);
}
privateintmaxValue(TreeNode root) {
TreeNode current = root; // loop down to find the leftmost leafwhile (current.right!=null) {
current = current.right;
}
return (current.val);
}
}
classSolution:
defisValidBST(self, root: Optional[TreeNode]) -> bool:
ifnot root:
returnTrue# Helper to find the maximum value in an entire subtreedefmax_value(node: Optional[TreeNode]) -> float:
ifnot node:
return float('-inf')
return max(node.val, max_value(node.left), max_value(node.right))
# Helper to find the minimum value in an entire subtreedefmin_value(node: Optional[TreeNode]) -> float:
ifnot node:
return float('inf')
return min(node.val, min_value(node.left), min_value(node.right))
# 1. False if the max of the left subtree is >= current node's valueif root.left and max_value(root.left) >= root.val:
returnFalse# 2. False if the min of the right subtree is <= current node's valueif root.right and min_value(root.right) <= root.val:
returnFalse# 3. False if, recursively, the left or right subtree is not a valid BSTifnot self.isValidBST(root.left) ornot self.isValidBST(root.right):
returnFalse# If all checks pass, it's a valid BST at this nodereturnTrue
All values in the left subtree must be less than the root, and all values in the right subtree must be greater than the root. Therefore, we check each node’s value against these boundaries. This method traverses the left subtree first. If a violation occurs near the root but on the right subtree, the time complexity remains O(n). However, the second solution below can detect violations near the root more quickly.
classSolution {
publicbooleanisValidBST(TreeNode root) {
// Initialize with the full range of long values.return isValid(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
privatebooleanisValid(TreeNode node, long min, long max) {
if (node ==null) {
returntrue;
}
// Check if the current node's value is within the valid range.if (node.val<= min || node.val>= max) {
returnfalse;
}
// Recursively check the left and right subtrees with updated bounds.// For the left child, the parent's value is the new max.// For the right child, the parent's value is the new min.return isValid(node.left, min, node.val) && isValid(node.right, node.val, max);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
classSolution {
publicbooleanisValidBST(TreeNode root) {
return helper(root, null, null);
}
// We are using long min and long max to handle the case// when node's value is Integer.MIN_VALUE OR Integer.MAX_VALUEprivatebooleanhelper(TreeNode root, Integer min, Integer max) {
if (root ==null) {
returntrue;
}
return (min ==null|| root.val> min) && (max ==null|| root.val< max) && helper(root.left, min, root.val) && helper(root.right, root.val, max);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classSolution {
publicbooleanisValidBST(TreeNode root) {
return helper(root, null, null);
}
// We are using long min and long max to handle the case// when node's value is Integer.MIN_VALUE OR Integer.MAX_VALUEprivatebooleanhelper(TreeNode root, TreeNode min, TreeNode max) {
if (root ==null) {
returntrue;
}
return (min ==null|| root.val> min.val) && (max ==null|| root.val< max.val) && helper(root.left, min, root) && helper(root.right, root, max);
}
}
⏰ Time complexity: O(n), because we visit each node exactly once.
🧺 Space complexity: O(n) for the recursion stack in the worst case of a completely unbalanced tree.
Method 4 - Iterative Using Queue and Node Modification#
We can create a new BTNode with long left and right values, denoting the smallest value on the left and max value on right. Each time, we add value to queue, we set the min and max we have seen so far, and when we poll out the values should be in the range [left, right].
1
2
3
4
5
6
7
8
9
10
11
//define a BNode class with TreeNode and it's boundariesclassBNode {
TreeNode n;
long left;
long right;
publicBNode(TreeNode n, long left, long right) {
this.n= n;
this.left= left;
this.right= right;
}
}
⏰ Time complexity: O(n), as each node in the tree is visited exactly once.
🧺 Space complexity: O(n) in the worst case, because the queue will have to store all nodes at the widest level of the tree, which can be up to n/2 nodes for a complete tree.
Method 5 - Inorder Traversal (with and without Aux array)#
Perform an in-order traversal of the tree and store the results in a temporary array.
Verify if the temporary array is sorted in ascending order; if so, the tree is a BST.
To avoid using an auxiliary array, we can track the previously visited node during the in-order traversal. If the current node’s value is less than the previous node’s value, the tree is not a BST.
Similar to recursive DFS, we can also perform iterative DFS using stack.
The below code is just a modified version of the standard non-recursive Breadth First Search (BFS) of a binary tree. If you are unfamiliar with the algorithm for BFS, you can find Binary Tree Level Order Traversal.
To check if each node adheres to the rules of a binary search tree (BST), we verify that each left child’s value is less than the current node’s value and each right child’s value is greater. A binary tree is considered a BST if all nodes satisfy these conditions. If all nodes are validated without any violations, we return true; otherwise, we return false upon detecting a single violation.
classSolution {
// Helper class to store a node and its valid rangeprivatestaticclassNodeState {
public TreeNode node;
public Long min;
public Long max;
publicNodeState(TreeNode node, Long min, Long max) {
this.node= node;
this.min= min;
this.max= max;
}
}
publicbooleanisValidBST(TreeNode root) {
if (root ==null) {
returntrue;
}
// Use a stack for iterative DFS Stack<NodeState> stack =new Stack<>();
// Start with the root node. It has no restrictions, so we use null // for min and max to represent negative and positive infinity. stack.push(new NodeState(root, null, null));
while (!stack.isEmpty()) {
NodeState current = stack.pop();
// Check if the node's value is outside its allowed rangeif (current.min!=null&& current.node.val<= current.min) {
returnfalse;
}
if (current.max!=null&& current.node.val>= current.max) {
returnfalse;
}
// If the right child exists, push it to the stack with an updated min bound.// Its new min is its parent's value.if (current.node.right!=null) {
stack.push(new NodeState(current.node.right, (long) current.node.val, current.max));
}
// If the left child exists, push it to the stack with an updated max bound.// Its new max is its parent's value.if (current.node.left!=null) {
stack.push(new NodeState(current.node.left, current.min, (long) current.node.val));
}
}
// If we traverse the whole tree without finding an invalid node, it's a valid BST.returntrue;
}
}