98. Validate Binary Search Tree - Explanation

Problem Link

Description

Given the root of a binary tree, return true if it is a valid binary search tree, otherwise return false.

A valid binary search tree satisfies the following constraints:

  • The left subtree of every node contains only nodes with keys less than the node's key.
  • The right subtree of every node contains only nodes with keys greater than the node's key.
  • Both the left and right subtrees are also binary search trees.

Example 1:

Input: root = [2,1,3]

Output: true

Example 2:

Input: root = [1,2,3]

Output: false

Explanation: The root node's value is 1 but its left child's value is 2 which is greater than 1.

Constraints:

  • 1 <= The number of nodes in the tree <= 1000.
  • -1000 <= Node.val <= 1000


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(n) time and O(n) space, where n is the number of nodes in the given tree.


Hint 1

A brute force solution would involve traversing the tree and, for every node, checking its entire left subtree to ensure all nodes are less than the current node, and its entire right subtree to ensure all nodes are greater. This results in an O(n^2) solution. Can you think of a better way? Maybe tracking values during the traversal would help.


Hint 2

We can use the Depth First Search (DFS) algorithm to traverse the tree. At each node, we need to ensure that the tree rooted at that node is a valid Binary Search Tree (BST). One way to do this is by tracking an interval that defines the lower and upper limits for the node's value in that subtree. This interval will be updated as we move down the tree, ensuring each node adheres to the BST property.


Hint 3

We start with the interval [-infinity, infinity] for the root node. As we traverse the tree, when checking the left subtree, we update the maximum value limit because all values in the left subtree must be less than the current node's value. Conversely, when checking the right subtree, we update the minimum value limit because all values in the right subtree must be greater than the current node's value.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Search Tree Properties - Understanding that left subtree values must be less than the node and right subtree values must be greater
  • Tree Traversal (DFS/BFS) - Navigating through tree nodes using depth-first or breadth-first approaches
  • Recursion - Passing constraints (valid ranges) down through recursive calls

1. Brute Force

Intuition

To check if a tree is a valid Binary Search Tree (BST), every node must satisfy:

  • All values in its left subtree are strictly less than the node’s value.
  • All values in its right subtree are strictly greater than the node’s value.

In this brute force idea, for each node we:

  1. Check all nodes in its left subtree to confirm they are < node.val.
  2. Check all nodes in its right subtree to confirm they are > node.val.
  3. Then recursively repeat the same process for each child as a new root.

This re-checks many nodes multiple times, so it’s correct but not efficient.

Algorithm

  1. If the tree is empty → return true.
  2. For the current node:
    • Run a helper on its left subtree to ensure every value < current value.
    • Run a helper on its right subtree to ensure every value > current value.
    • If either check fails → return false.
  3. Recursively:
    • Check that the left child's subtree is a valid BST.
    • Check that the right child's subtree is a valid BST.
  4. If all checks pass for every node → return true.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    left_check = staticmethod(lambda val, limit: val < limit)
    right_check = staticmethod(lambda val, limit: val > limit)

    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True

        if (not self.isValid(root.left, root.val, self.left_check) or
            not self.isValid(root.right, root.val, self.right_check)):
            return False

        return self.isValidBST(root.left) and self.isValidBST(root.right)

    def isValid(self, root: Optional[TreeNode], limit: int, check) -> bool:
        if not root:
            return True
        if not check(root.val, limit):
            return False
        return (self.isValid(root.left, limit, check) and
                self.isValid(root.right, limit, check))

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n)O(n)

Intuition

A Binary Search Tree isn’t just about each node being smaller or larger than its parent —
every node must fit within a valid value range decided by all its ancestors.

  • For the root, the allowed range is (-∞, +∞).
  • When you go left, the node’s value must be less than the parent, so the upper bound becomes smaller.
  • When you go right, the node’s value must be greater than the parent, so the lower bound becomes larger.

As we move down the tree, we keep tightening these bounds.
If any node violates its allowed range → the tree is not a BST.

This checks all BST rules efficiently in one DFS pass.

Algorithm

  1. Start DFS from the root with the initial valid range (-∞, +∞).
  2. For each node:
    • If node.val is not strictly between (left, right) → return false.
  3. Recursively:
    • Validate the left subtree with updated range (left, node.val).
    • Validate the right subtree with updated range (node.val, right).
  4. If all nodes satisfy their ranges → it is a valid BST.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        def valid(node, left, right):
            if not node:
                return True
            if not (left < node.val < right):
                return False

            return valid(node.left, left, node.val) and valid(
                node.right, node.val, right
            )

        return valid(root, float("-inf"), float("inf"))

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Intuition

A tree is a valid BST only if every node lies within a valid range defined by its ancestors.
Instead of using recursion, we can use a queue (BFS) to check this level by level.

  • Start with the root, whose valid range is (-∞, +∞).
  • When moving to the left child, its maximum allowed value becomes the current node’s value.
  • When moving to the right child, its minimum allowed value becomes the current node’s value.
  • If any node violates its allowed range, the tree is not a BST.

This way, we verify every node exactly once using BFS.

Algorithm

  1. If the tree is empty → return true.
  2. Push (root, -∞, +∞) into a queue.
  3. While the queue is not empty:
    • Pop (node, leftBound, rightBound).
    • If node.val is not between (leftBound, rightBound), return false.
    • Push the left child with range (leftBound, node.val).
    • Push the right child with range (node.val, rightBound).
  4. If all nodes satisfy their ranges → return true.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True

        q = deque([(root, float("-inf"), float("inf"))])

        while q:
            node, left, right = q.popleft()
            if not (left < node.val < right):
                return False
            if node.left:
                q.append((node.left, left, node.val))
            if node.right:
                q.append((node.right, node.val, right))

        return True

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Common Pitfalls

Only Comparing with the Immediate Parent

A classic mistake is checking only that left.val < node.val < right.val for direct children. This misses cases where a node deep in the left subtree has a value greater than an ancestor. For example, a tree with root 10, left child 5, and right grandchild 15 under node 5 would pass local checks but violates BST properties. The valid range must be inherited from all ancestors.

Using Inclusive Bounds Instead of Exclusive

BST properties require strictly less than and strictly greater than comparisons. Using <= or >= instead of < and > will incorrectly accept trees with duplicate values, which violates the standard BST definition where all left descendants are strictly less and all right descendants are strictly greater.

Integer Overflow with Boundary Values

When using Integer.MIN_VALUE and Integer.MAX_VALUE as initial bounds, problems arise if the tree contains these exact values as node data. The comparison node.val > Integer.MAX_VALUE will never be true. Use Long.MIN_VALUE and Long.MAX_VALUE or nullable types to handle edge cases where node values equal the integer limits.