Problem Walkthrough

Validate BST LeetCode 98 Deep Dive

Solve LeetCode 98 Validate Binary Search Tree using three distinct approaches: the min/max bounds recursion that propagates ancestor constraints downward, the inorder traversal technique that leverages the sorted-order property of BSTs, and Morris traversal for O(1) space inorder checking without a recursion stack.

9 min read|

Validate BST

LeetCode 98

Problem Overview

LeetCode 98 — Validate Binary Search Tree — asks you to determine whether a given binary tree satisfies the BST property: for every node, all values in its left subtree must be strictly less than the node's value, and all values in its right subtree must be strictly greater. This constraint applies to entire subtrees, not just immediate children.

The problem appears in nearly every major interview loop covering trees, because it exposes a critical conceptual trap and tests whether the candidate understands BST semantics at depth. A tree can look locally valid at every parent-child pair yet still be a globally invalid BST if a deeply nested node violates an ancestor constraint.

The constraints are deliberately permissive: the tree can have up to 10,000 nodes, and node values range from -2^31 to 2^31 - 1. The value range matters — it rules out using 0 or Integer.MIN_VALUE as sentinel bounds, and requires float("-inf") in Python or Long.MIN_VALUE/Long.MAX_VALUE in Java to safely initialize the recursive bounds.

  • Input: root of a binary tree with up to 10,000 nodes
  • Output: true if the tree is a valid BST, false otherwise
  • Left subtree: ALL values must be strictly less than node.val
  • Right subtree: ALL values must be strictly greater than node.val
  • Node values: 32-bit signed integers — do not use int min/max as sentinels
  • Applies recursively to every subtree, not just immediate parent-child pairs

Common Mistake — Only Checking Children

The most common error candidates make is checking only the immediate children: verify that node.left.val < node.val and node.right.val > node.val, then recurse. This passes many test cases but fails on trees where a deeply nested node violates an ancestor constraint. The classic counterexample: a tree rooted at 5, with left child 4, and a right child of 4 with its own right child 6. Every parent-child pair looks valid, but 6 is in the left subtree of 5, so it must be less than 5 — and it is not.

This mistake is so common that LeetCode designed the problem's canonical test cases specifically to expose it. The fix is not to compare adjacent nodes — it is to propagate inherited bounds downward. Every time you recurse left, the current node's value becomes a new upper bound that the entire left subtree must respect. Every time you recurse right, the current node's value becomes a new lower bound.

Understanding this distinction — local validity versus global validity — is the key insight for this problem and for understanding BSTs in general. A BST's power comes from the global invariant: every node is bounded not just by its parent but by every ancestor on its path from the root.

💡

Pass Bounds Downward, Not Just Check Children

The trick is passing min/max bounds downward: each node must satisfy bounds inherited from all ancestors, not just its immediate parent. When you recurse left, pass node.val as the new upper bound. When you recurse right, pass node.val as the new lower bound. Start with bounds of -infinity and +infinity at the root so every value at the root is initially valid.

Min/Max Bounds Approach

The bounds approach defines a helper validate(node, minVal, maxVal) that returns true if the subtree rooted at node is a valid BST with all values strictly between minVal and maxVal. At the root, call validate(root, -infinity, +infinity). Every node must satisfy minVal < node.val < maxVal; if it does not, return false immediately.

When recursing left, the current node's value becomes the new upper bound — the entire left subtree must have values less than node.val. When recursing right, node.val becomes the new lower bound. These updated bounds propagate downward through the entire tree, ensuring that every node respects every ancestor constraint, not just its immediate parent.

This approach runs in O(n) time since each node is visited exactly once, and uses O(h) space for the recursion stack where h is the tree height — O(log n) for balanced trees and O(n) for skewed trees. In Python use float("-inf") and float("inf"). In Java use Long.MIN_VALUE and Long.MAX_VALUE to avoid integer overflow when node values sit at the 32-bit boundary.

  1. 1Define validate(node, minVal, maxVal) returning bool
  2. 2Base case: if node is None/null, return True (empty subtree is valid)
  3. 3If node.val <= minVal or node.val >= maxVal: return False
  4. 4Recurse left: validate(node.left, minVal, node.val)
  5. 5Recurse right: validate(node.right, node.val, maxVal)
  6. 6Return True only if both left and right subtrees are valid
  7. 7Initial call: validate(root, float("-inf"), float("inf"))

Inorder Traversal Approach

The inorder traversal approach leverages a fundamental BST property: an inorder traversal of a valid BST always produces values in strictly increasing order. This transforms the validation problem into a sequence problem — perform an inorder traversal and verify that each value is strictly greater than the previous one. If any violation is found, the tree is not a valid BST.

Implementation uses a single pointer or instance variable to track the previously visited inorder value, initialized to negative infinity. During inorder traversal — left subtree, current node, right subtree — compare each node's value against the previous. If current.val is not strictly greater than previous, return false immediately. Otherwise update previous and continue.

The inorder approach has the same O(n) time and O(h) space as the bounds approach. Its advantage is conceptual clarity: it makes the connection between BSTs and sorted sequences explicit. Many candidates find it easier to reason about — instead of thinking about abstract bounds, you are simply checking if the traversal output is sorted. The iterative version using an explicit stack avoids recursion depth limits for very deep trees.

ℹ️

Inorder Traversal Is the Most Intuitive Approach

Inorder traversal is the most intuitive approach: a valid BST is equivalent to a sorted inorder sequence, which is a powerful mental model. Instead of reasoning about min/max bounds propagating through recursive calls, you simply ask: does traversing this tree in order produce a strictly increasing list? This connection between BSTs and sorted arrays is fundamental and appears across dozens of BST problems.

Morris Traversal for O(1) Space

Morris traversal is a threading technique that performs an inorder traversal of a binary tree without using a recursion stack or an explicit stack data structure. It temporarily modifies the tree's right pointers to create threads — shortcuts back to inorder successors — and then restores them after visiting each node. The result is O(n) time and O(1) extra space.

The algorithm maintains a current pointer starting at root. For each node, if it has no left child, visit the node and move right. If it has a left child, find the inorder predecessor (rightmost node in the left subtree). If the predecessor's right pointer is null, set it to current (create a thread) and move left. If the predecessor's right points back to current, the left subtree has been processed: unthread it, visit current, and move right.

For BST validation using Morris traversal, check the BST property while visiting each node during the traversal: compare the current node's value against the previously visited value and return false if the current is not strictly greater. This gives O(n) time with O(1) extra space — optimal for memory-constrained environments where the tree could be extremely deep and the call stack is limited.

  1. 1Initialize current = root, prev = -infinity
  2. 2While current is not null:
  3. 3If current has no left child: check BST property (current.val > prev), update prev, move current = current.right
  4. 4Else find inorder predecessor (rightmost in current.left subtree)
  5. 5If predecessor.right is null: set predecessor.right = current (thread), move current = current.left
  6. 6If predecessor.right == current: unthread (predecessor.right = null), check BST property, update prev, move current = current.right

Code Walkthrough Python and Java

Python bounds version: def isValidBST(root): def validate(node, min_val, max_val): if not node: return True; if node.val <= min_val or node.val >= max_val: return False; return validate(node.left, min_val, node.val) and validate(node.right, node.val, max_val); return validate(root, float("-inf"), float("inf")). Clean single-function solution with no helper class needed.

Python inorder version using prev pointer: self.prev = float("-inf") at the class level, then def inorder(node): if not node: return True; if not inorder(node.left): return False; if node.val <= self.prev: return False; self.prev = node.val; return inorder(node.right). Java equivalent uses a long[] prev = {Long.MIN_VALUE} array to pass by reference across recursive calls, avoiding a class field.

Time complexity for all three approaches is O(n) — each node is visited exactly once. Space complexity for the recursive bounds and inorder approaches is O(h) for the call stack, where h is the tree height. Morris traversal achieves O(1) extra space at the cost of temporarily modifying the tree. For interviews, the bounds approach is the standard answer; mention Morris traversal as the space-optimal alternative if asked for optimization.

⚠️

Use Strict Inequality — No Duplicate Values in BST

Use strict inequality: BST requires left < node < right, not <=. Duplicate values make the tree invalid per LeetCode's definition of a BST. This means a node whose value equals its parent, or equals a bound value, must return false. Be especially careful with the bounds initialization — use float("-inf") and float("inf") in Python, or Long.MIN_VALUE and Long.MAX_VALUE in Java, not Integer bounds, to avoid false negatives when node values are exactly at 32-bit limits.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now