Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 32

Validating a Binary Search Tree

Recommended resources

Hello! Welcome to your next lesson on Binary Search Trees.

In the previous lesson, we implemented the core operations of a BST: search, insertion, and deletion. We saw how the fundamental BST property—that for any node, all values in its left subtree are smaller and all values in its right subtree are larger—drives the logic for these operations.

Today, we'll tackle a classic and insightful interview problem that directly tests your understanding of this property. The learning outcome is to write a function to validate if a given binary tree is a valid BST. This task seems simple at first glance, but it contains a subtle trap that many candidates fall into. Mastering this problem demonstrates a deep and precise understanding of BSTs, which is exactly what interviewers are looking for.

The Obvious—But Flawed—Approach

Let's start by considering the definition of a BST. For any node, its left child must be smaller and its right child must be larger. A first attempt might be to write a function that recursively checks this for every node:

# THIS IS AN INCORRECT SOLUTION
def is_valid_bst_naive(node):
    if not node:
        return True
    
    # Check if the left child is smaller (if it exists)
    if node.left and node.left.val >= node.val:
        return False
        
    # Check if the right child is larger (if it exists)
    if node.right and node.right.val <= node.val:
        return False
        
    # Recursively check the subtrees
    return is_valid_bst_naive(node.left) and is_valid_bst_naive(node.right)

This approach seems logical, but it's wrong. Why? Because it only checks the relationship between a node and its immediate children. It fails to enforce the global BST property.

To see why, let's watch a short video that highlights this exact pitfall with a clear example.

Validate Binary Search Tree - Depth First Search - Leetcode 98

The video 'Validate Binary Search Tree' by NeetCode starts by demonstrating why simply checking parent-child relationships is not enough. Pay close attention to the example tree he uses to illustrate the flaw.

Watch the segment from the beginning until 02:28. The key is understanding the counter-example and why the simple check fails.

The video uses an example like this:

      5
     / \
    1   7
       / \
      4   8

In this tree, 4 is correctly the left child of 7 (since 4 < 7), but it violates the overall BST property because 4 is in the right subtree of the root 5, and it is not greater than 5. The naive check would miss this.

This image provides another excellent visual comparison of a valid and an invalid BST, highlighting a similar violation.

BST Validation: Valid vs. Invalid Examples
Caption: In the "INVALID" tree, node 11 is correctly to the right of 10, but it violates the rule that everything in the right subtree of the root (25) must be greater than 25.

The key takeaway is that every node must be within a range defined by all of its ancestors, not just its parent.


Approach 1: Recursive Validation with Boundaries

The most robust and intuitive solution is to perform a recursive traversal while keeping track of the valid range of values for each node.

The logic is as follows:

  1. Start at the root. The valid range for the root is (-infinity, +infinity).
  2. For any given node, check if its value falls within its valid range. If not, the tree is invalid.
  3. When you recurse to the left child, the valid range narrows. The new upper bound becomes the parent's value. The lower bound stays the same. The range becomes (low, parent.val).
  4. When you recurse to the right child, the valid range also narrows. The new lower bound becomes the parent's value. The upper bound stays the same. The range becomes (parent.val, high).

This video segment explains the boundary-passing technique very clearly.

Validate Binary Search Tree - Depth First Search - Leetcode 98

Continuing with the NeetCode video, this part explains the core logic of using left and right boundaries (min/max values) to correctly validate the tree.

Watch the segment from 02:28 to 06:44. Focus on how the left and right boundaries are updated as the algorithm descends into the left and right subtrees.

Let's solidify this concept by looking at a well-structured article that presents the full Python implementation.

Mastering LeetCode: Binary Search Tree Validation

The article 'Mastering LeetCode: Binary Search Tree Validation' provides a great explanation and implementation of the recursive approach.

Please read the section titled 'Recursive Approach to Validate a BST'. Review the code and the step-by-step breakdown. Note how float('-inf') and float('inf') are used for the initial call.

Here is the code from the article, which elegantly implements this logic using a helper function:

from typing import Optional

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def isValidBST(root: Optional[TreeNode]) -> bool:
    
    def validate(node: Optional[TreeNode], low: float, high: float) -> bool:
        # An empty tree is a valid BST
        if not node:
            return True
            
        # The current node's value must be between low and high (exclusive)
        if not (low < node.val < high):
            return False
            
        # Recursively validate the left and right subtrees with updated bounds
        return (validate(node.left, low, node.val) and
                validate(node.right, node.val, high))

    # Initial call for the root with the widest possible range
    return validate(root, float('-inf'), float('inf'))
  • Time Complexity: , where is the number of nodes, because we visit each node exactly once.
  • Space Complexity: , where is the height of the tree. This space is used by the recursion call stack. In the worst case of a skewed tree, this can be . In a balanced tree, it's .
Test your understanding!

Consider the invalid tree from our earlier example:

      5
     / \
    1   7
       / \
      4   8

Trace the validate(node, low, high) function for the node with value 4. What are the values of low and high when the function is called for this node, and why does it return False?

Show answer
  1. Start with validate(root=5, low=-inf, high=+inf). This is valid.
  2. The call for the right subtree is validate(node=7, low=5, high=+inf). This is also valid.
  3. From node 7, the call for its left child is validate(node=4, low=5, high=7).
  4. Inside this call, the condition low < node.val < high becomes 5 < 4 < 7. This is False, because 5 < 4 is false. The function immediately returns False, correctly identifying the tree as invalid.

Approach 2: In-Order Traversal

There is another elegant approach that relies on a key property of BSTs: an in-order traversal of a valid BST visits the nodes in ascending sorted order.

Recall that an in-order traversal follows the Left -> Root -> Right pattern. Because all nodes in the left subtree are smaller than the root, and all nodes in the right subtree are larger, this traversal naturally produces a sorted sequence.

To validate a BST using this property, we can perform an in-order traversal and simply check if each node's value is strictly greater than the value of the previously visited node.

This video provides an excellent explanation of this concept.

Validate Binary Search Tree (LeetCode 98) | Full solution with animations and visuals

The video by Nikhil Lohia explains how the in-order traversal property can be used for validation. It's a different way of thinking about the problem that is very powerful.

Watch the segment from 05:47 to 10:01. Focus on the core idea: if an in-order traversal yields a sorted sequence, the tree must be a BST.

While one way to implement this is to store the full in-order traversal in an array and check if it's sorted, that would require space. A more optimized approach for an interview is to perform an iterative in-order traversal and check the sorted property on the fly, which only requires space for the stack.

The article we looked at earlier also covers this optimized iterative implementation.

Mastering LeetCode: Binary Search Tree Validation

Let's return to the 'Mastering LeetCode' article to see the space-optimized in-order traversal solution.

Read the section titled 'In-Order Traversal Approach'. This presents an iterative implementation that keeps track of the previous value to ensure the sorted property holds.

Here is the iterative in-order code. It's a standard pattern for iterative traversal that you should be familiar with for interviews.

def isValidBST_inorder(root: Optional[TreeNode]) -> bool:
    stack = []
    current = root
    prev_value = float('-inf')

    while stack or current:
        # Traverse to the leftmost node
        while current:
            stack.append(current)
            current = current.left
        
        # Visit the node (pop from stack)
        current = stack.pop()
        
        # Check against the previous value
        if current.val <= prev_value:
            return False
        prev_value = current.val
        
        # Move to the right subtree
        current = current.right
        
    return True

This approach has the same time and space complexity as the recursive solution— time and space—but avoids the risk of a recursion depth error on very deep trees.


Conclusion

You've now learned two robust and efficient methods for validating a Binary Search Tree, a problem that effectively probes your fundamental understanding of this data structure.

Key Takeaways:

  • The Pitfall: BST validation is a global property, not a local one. Simply checking a node against its immediate children is insufficient.
  • Recursive Boundaries: The standard approach is a recursive traversal that passes down a (low, high) range, ensuring each node respects the constraints imposed by all its ancestors.
  • In-Order Property: A powerful alternative is to leverage the fact that an in-order traversal of a valid BST yields a sorted sequence. You can check this property on the fly during an iterative traversal.
  • Complexity: Both primary approaches achieve optimal performance with time complexity and space complexity.

In your next lesson, we will continue to build on these concepts by solving another common BST problem: finding the k-th smallest element in a BST. You'll see that the in-order traversal idea we discussed today is the key to solving that problem efficiently.