Skip to main content

Welcome to a Grasp lesson

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

Lesson 53

Binary Tree BST Validation

Recommended resources

Hello! Welcome back to our journey into Binary Search Trees.

In our last lesson, we tackled the complex logic of deleting nodes from a BST, ensuring the tree's properties were maintained. You learned to handle the three cases of deletion and saw how recursion can elegantly restructure the tree.

Today, we're shifting from modifying a BST to verifying one. We'll answer the question: "Given any binary tree, how can we determine if it's a valid Binary Search Tree?" This is a classic interview and competitive programming problem that tests your understanding of the core BST definition and your ability to spot subtle logical traps.

By the end of this lesson, you will be able to validate if a given binary tree is a BST, using efficient and robust algorithms.

The Full Definition of a BST

Let's quickly refresh the strict rules for a valid Binary Search Tree (BST):

  • For any given node N:
    • All values in its entire left subtree must be less than N.val.
    • All values in its entire right subtree must be greater than N.val.
  • Both the left and right subtrees must also be binary search trees.
  • There are no duplicate values (this is a common convention).

The key words here are entire subtree. This is where many simple validation attempts fail.


The Common Pitfall: Local vs. Global Properties

A common first thought is to traverse the tree and, for each node, check if its left child is smaller and its right child is larger.

if node.left.val < node.val and node.right.val > node.val:

This check is insufficient. A node must be greater than all nodes in its left subtree, not just its direct child.

Consider this tree:

Validate Binary Search Tree Problem Example
This image shows a tree rooted at 5. While 1 < 5 and 4 < 5, node 4 is in the right subtree of 5, which violates the BST property. A simple local check at node 5 would pass (1 < 5), and a local check at node 4's parent (which is not shown, but would be in the right subtree of 5) might also seem correct locally, but the global structure is invalid.

The value 4 in the right subtree violates the rule that all nodes in the right subtree of 5 must be greater than 5. A simple local check would miss this.

To understand this pitfall visually, watch the first few minutes of the following video.

Validate Binary Search Tree - Depth First Search - Leetcode 98

This video from NeetCode clearly explains why simply checking immediate children doesn't work and sets the stage for a correct algorithm.

Watch from 00:12 to 03:12. Focus on the example presented and understand why a naive, node-by-node check fails to identify the invalid BST.


The Correct Approach: Validating with Boundaries

To correctly validate a BST, we need to ensure that each node's value falls within a valid range, or boundary, defined by its ancestors. We can do this with a recursive traversal (like a pre-order traversal or DFS).

The logic is as follows:

  1. We start at the root. The root can have any value, so its valid range is from negative infinity to positive infinity (-∞, +∞).
  2. When we traverse to a left child, we know its value must be less than its parent. So, we tighten the valid range for the entire left subtree: the upper bound becomes the parent's value. The new range is (parent's lower_bound, parent's value).
  3. When we traverse to a right child, we know its value must be greater than its parent. So, we tighten the valid range for the entire right subtree: the lower bound becomes the parent's value. The new range is (parent's value, parent's upper_bound).
  4. At each node, we check if its value is within the (lower_bound, upper_bound) range passed down to it. If not, the tree is invalid. If we traverse the entire tree without any violations, it is a valid BST.

This image provides a great visualization of how the low and high boundaries are updated during the traversal.

Binary Search Tree Validation Example
This diagram illustrates the valid range (low, high) for each node. The root's range is infinite. As we go left from node 4 to node 2, the upper bound becomes 4. As we go right from node 4 to node 6, the lower bound becomes 4.

Let's watch this boundary-passing technique in action.

Validate Binary Search Tree - Depth First Search - Leetcode 98

The same NeetCode video continues by explaining the efficient O(N) approach using boundaries. Pay attention to how the left and right boundary values are passed down in the recursive calls.

Watch from 03:12 to 06:44. This section provides a step-by-step walkthrough of the boundary-checking algorithm on the invalid tree example.


Implementation with Recursive DFS

This algorithm is naturally implemented using a recursive helper function. The main function initiates the call with the root and infinite boundaries, and the helper function performs the traversal and validation.

For a clear, well-structured guide on implementing this, let's turn to CodePath's guide, which is designed with a problem-solving mindset similar to competitive programming.

Validate Binary Search Tree

This guide from CodePath provides excellent pseudocode and a clean Python implementation for the boundary-checking algorithm.

First, read the 'P-lan' section to understand the pseudocode and general idea. Then, study the Python code in the 'I-mplement' section. Notice how a helper function is used to pass the minimumValueOfNode and maximumValueOfNode parameters.

Here's the Python implementation from the guide, which you should now be able to understand fully. Note the use of math.inf for the initial boundaries.

import math

# Assume we have a TreeNode class defined like this:
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: TreeNode) -> bool:
        # Helper function processes the tree with min and max boundaries.
        def helper(node: TreeNode, min_val: float, max_val: float) -> bool:
            # An empty node or subtree is a valid BST.
            if not node:
                return True
            
            # Check if the node's value is outside its valid range.
            # Note: A valid BST typically doesn't allow equal values.
            if not (min_val < node.val < max_val):
                return False
            
            # Recursively check the left and right subtrees with updated boundaries.
            # The 'and' ensures both subtrees must be valid.
            return (helper(node.left, min_val, node.val) and
                    helper(node.right, node.val, max_val))

        # Initial call for the root node with infinite boundaries.
        return helper(root, -math.inf, math.inf)
Test your understanding!

Consider a tree with root = 10, root.left = 5, and root.right = 15. Let's say root.right.left = 6. Is this a valid BST? Trace the helper(node, min_val, max_val) calls to find out.

Show answer
  1. helper(node=10, min_val=-inf, max_val=inf): 10 is valid.
  2. The code then calls helper for the left child: helper(node=5, min_val=-inf, max_val=10). 5 is valid. This branch returns True.
  3. Next, it calls helper for the right child: helper(node=15, min_val=10, max_val=inf). 15 is valid.
  4. From node 15, it calls helper for its left child: helper(node=6, min_val=10, max_val=15).
  5. Inside this call, the condition min_val < node.val < max_val becomes 10 < 6 < 15, which is False.
  6. The function returns False for node 6, which propagates all the way up. The tree is not a valid BST.

An Alternative Method: In-Order Traversal

There's another elegant way to solve this problem that leverages a key property you've already used: an in-order traversal of a valid BST visits nodes in ascending sorted order.

Therefore, we can validate a tree by:

  1. Performing an in-order traversal.
  2. Checking if the sequence of visited nodes is strictly increasing.

A naive way is to store all node values in a list during the traversal and then check if the list is sorted. However, this uses O(N) extra space for the list.

A more optimized approach avoids storing the whole list. During the in-order traversal, you can simply keep track of the value of the previously visited node. If the current node's value is not strictly greater than the previous node's value, you've found a violation.

Check if a Binary Tree is BST or not

The GeeksforGeeks article provides a great explanation of this in-order traversal approach, including the optimized version that doesn't require an extra array.

Read 'Approach - 2: Using Inorder Traversal'. Pay attention to the note about avoiding an auxiliary array and how the prev value is used in the Python code to check the sorted property on the fly.


Complexity Analysis

For competitive programming, analyzing the efficiency of your solution is critical.

  • Time Complexity: Both the boundary-checking method and the in-order traversal method visit each node exactly once. Therefore, the time complexity for both is O(N), where N is the number of nodes in the tree.
  • Space Complexity:
    • Boundary-Checking Method: The space is determined by the depth of the recursion stack. In a balanced tree, this is O(log N). In the worst case of a completely skewed tree (like a linked list), it is O(N).
    • In-Order Traversal Method: If you store the nodes in a list, the space complexity is O(N). For the optimized approach that only tracks the previous value, the space complexity is determined by the recursion stack, making it the same as the boundary-checking method: O(N) in the worst case.

Conclusion

In this lesson, you learned how to rigorously validate a binary tree to see if it conforms to the BST properties. This is a fundamental skill that reinforces your understanding of both BSTs and tree traversal algorithms.

Key Takeaways:

  • Validating a BST requires checking global properties, not just the local relationship between a parent and its immediate children.
  • The boundary-checking (or range-checking) method is a robust way to validate a BST. It uses a recursive function that passes down a (min_val, max_val) range for each node.
  • An alternative approach is to use an in-order traversal and verify that the nodes are visited in strictly ascending order.
  • Both primary methods have a time complexity of O(N) and a worst-case space complexity of O(N) due to the recursion stack.

Up Next:
We've just seen how powerful in-order traversal is for validation. In the next lesson, we will formalize our knowledge of tree traversals and focus on performing an in-order traversal on a BST to retrieve elements in sorted order. We'll also explore its iterative implementation using a stack, a crucial technique in competitive programming to avoid recursion depth limits and manage memory more explicitly.