Skip to main content

Welcome to a Grasp lesson

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

Lesson 50

Validating a Binary Search Tree

Recommended resources

Hello! Welcome back to our journey through search trees.

In the last lesson, we built a Binary Search Tree from the ground up, implementing insertion, search, and the tricky deletion operation. We established the fundamental rule of a BST: for any node, everything in its left subtree must be smaller, and everything in its right subtree must be larger.

Today, we'll tackle a classic and crucial interview problem that directly tests this understanding: how to validate if a given binary tree is a valid BST. It seems simple on the surface, but it contains a subtle trap that many developers fall into. Mastering this will deepen your understanding of tree properties and recursive thinking.

1. The Obvious (and Incorrect) Approach

Let's start by defining the BST property again:

  1. A node's left subtree contains only nodes with keys less than the node's key.
  2. A node's right subtree contains only nodes with keys greater than the node's key.
  3. Both the left and right subtrees must also be binary search trees.

A first-instinct solution might be to traverse the tree and, for each node, check if its left child is smaller and its right child is larger.

// A tempting but FLAWED approach
boolean isBST(Node node) {
    if (node == null) return true;
    
    // Check local properties
    if (node.left != null && node.left.key >= node.key) return false;
    if (node.right != null && node.right.key <= node.key) return false;
    
    // Recurse on children
    return isBST(node.left) && isBST(node.right);
}

This seems logical, but it's wrong. Why? Because this check is only local. The BST property must be global. A node's value must be correct relative to all its ancestors, not just its immediate parent.

To see why this fails, let's watch a short segment of a video that provides a perfect example.

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

This video from Nikhil Lohia clearly demonstrates the pitfall of only checking immediate parent-child relationships.

Watch from 03:26 to 05:47. Pay close attention to the example where the node with value 10 is added. Notice how it satisfies the local property with its parent (15) but violates the global property with its grandparent (11).

As the video showed, a node like 10 can be less than its parent 15, but because it's in the right subtree of 11, it violates the rule that all nodes in the right subtree must be greater than 11.

Validating Binary Search Trees (BSTs)
This image shows examples of invalid BSTs. In the second tree, `6` is incorrectly placed because it's in the right subtree of `7` but is smaller. A simple local check might miss such global violations.

Now that we understand the problem's subtlety, let's explore two correct and efficient solutions.


2. Approach 1: Recursive Range Checking

The most robust way to solve this is to ensure each node's value falls within a valid range (min, max). We pass this range down during our traversal and tighten the constraints at each level.

Here's the logic:

  • The root of the tree can be any value, so its initial range is (-∞, +∞).
  • When we traverse to the left child of a node N with value v, we know all nodes in this subtree must be less than v. So, the new valid range for the left child becomes (min_from_parent, v).
  • When we traverse to the right child of N, we know all nodes in this subtree must be greater than v. So, the new valid range becomes (v, max_from_parent).

If any node's value falls outside the (min, max) range it's supposed to be in, the tree is not a valid BST.

This video provides a concise walkthrough of this approach.

[Java] Leetcode 98. Validate Binary Search Tree [Binary Search Tree #3]

This video from Eric Programming explains the range-checking method clearly and provides a simple implementation.

Watch from 01:16 to 06:42. Focus on how the min and max bounds are updated in the recursive calls for the left and right subtrees.

Here is how you would structure this in Java. Notice the use of a helper function, a common pattern in recursive tree problems.

// We use Long for the min/max range to handle cases where a node's value is
// Integer.MIN_VALUE or Integer.MAX_VALUE.
public boolean isValidBST(Node root) {
    return isValid(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean isValid(Node node, long min, long max) {
    // Base case: An empty tree is a valid BST.
    if (node == null) {
        return true;
    }

    // Check if the current node's value is within its valid range.
    if (node.key <= min || node.key >= max) {
        return false;
    }

    // Recursively check the subtrees with updated ranges.
    // For the left subtree, the new max is the current node's key.
    // For the right subtree, the new min is the current node's key.
    return isValid(node.left, min, node.key) && 
           isValid(node.right, node.key, max);
}
Test your understanding!

Consider a tree with root 10. Its left child is 5 and its right child is 15. The right child of 15 is 20, and its left child is 6.

  1. The initial call is isValid(10, Long.MIN_VALUE, Long.MAX_VALUE).
  2. What are the min and max values for the recursive call on node 15?
  3. What are the min and max values for the recursive call on node 6?
  4. At which node does the validation fail?
Show answer
  1. Initial call is isValid(10, Long.MIN_VALUE, Long.MAX_VALUE).
  2. The call for the right child 15 will be isValid(15, 10, Long.MAX_VALUE). The node 15 is valid since 10 < 15 < Long.MAX_VALUE.
  3. The call for the left child 6 (from parent 15) will be isValid(6, 10, 15).
  4. The validation fails at node 6. The condition node.key <= min becomes 6 <= 10, which is true, causing the function to return false. Node 6 violates the global BST property because it's in the right subtree of 10 but is not greater than 10.

3. Approach 2: In-order Traversal

There's another clever approach that leverages a key property we've seen before: an in-order traversal of a valid BST visits the nodes in ascending sorted order.

This insight changes the problem: we can perform an in-order traversal and simply check if the sequence of node values is sorted.

While you could store the entire in-order traversal in a list and check it afterwards, that would require extra space. A more optimized solution uses space (for the recursion stack) by checking the order on the fly.

The logic is:

  1. During the in-order traversal (left -> root -> right), keep track of the value of the previously visited node.
  2. When you visit the current node, compare its value to the previous node's value.
  3. If the current value is not strictly greater than the previous value, the tree is not a valid BST.
  4. After visiting the current node, update the previous value to the current node's value before traversing the right subtree.

Let's read about this approach and see its implementation.

Check if a Binary Tree is BST or not

This GeeksforGeeks article explains the in-order traversal method. We'll focus on the optimized approach that avoids using an extra array.

Read 'Approach - 2] Using Inorder Traversal'. Pay special attention to the note: 'We can avoid the use of an Auxiliary Array. While doing In-Order traversal, we can keep track of previously visited value.' Then, examine the provided Java implementation to see how a prev pointer is used to achieve this.

The optimized in-order approach is often considered more elegant, but it can be trickier to implement correctly in an interview, especially iteratively. Let's look at a recursive Java implementation. A simple way to handle the previous value across recursive calls is to use a member variable or a boxed integer wrapper.

public class Solution {
    // Using a boxed type so it can be null for the first node.
    private Integer prev = null;

    public boolean isValidBST(Node root) {
        return inorder(root);
    }

    private boolean inorder(Node node) {
        if (node == null) {
            return true;
        }

        // 1. Traverse left subtree
        if (!inorder(node.left)) {
            return false;
        }

        // 2. Visit current node
        // For the very first node, prev is null, so this check is skipped.
        // For all subsequent nodes, check if the order is maintained.
        if (prev != null && node.key <= prev) {
            return false;
        }
        // Update prev to the current node's key
        prev = node.key;

        // 3. Traverse right subtree
        return inorder(node.right);
    }
}

Note: In an interview, be careful with mutable state like the prev field if the Solution object is reused across multiple test cases. You would need to reset it before each top-level call.


4. Complexity Analysis

Both correct approaches are efficient. Let be the number of nodes and be the height of the tree.

  • Range-Checking Method:

    • Time Complexity: . We visit every node exactly once.
    • Space Complexity: . This space is used by the recursion call stack. For a balanced tree, . For a completely unbalanced (skewed) tree, .
  • In-order Traversal Method (Optimized):

    • Time Complexity: . We still visit every node once.
    • Space Complexity: . Similar to the range-checking method, the space is dominated by the recursion stack.

Both methods are excellent, and you should be prepared to explain and implement either one in an interview. The range-checking method is often considered slightly more direct and less prone to implementation errors with state management.


Conclusion

Fantastic work! You've now mastered a classic tree problem that moves beyond simple implementation to validation and logical reasoning. This is exactly the kind of problem you'll face in technical interviews.

Key Takeaways:

  • Validating a BST requires a global property check, not just a local one between a parent and its immediate children.
  • The recursive range-checking method is a robust solution that passes (min, max) bounds down the tree, ensuring every node fits its global constraints.
  • The in-order traversal method provides an alternative solution by leveraging the property that an in-order traversal of a valid BST yields a sorted sequence. The space-optimized version checks this on the fly.
  • Both optimal solutions have a time complexity of and space complexity of .

Preview of the Next Lesson:
In both this and the previous lesson, we've hinted at the major weakness of a standard BST: its performance degrades to if the tree becomes unbalanced. How do we prevent this? The solution is self-balancing trees. In our next lesson, we will explain the need for self-balancing trees and dive into our first example by performing AVL tree rotations.