Start learning free

LESSON 408 OF 536 · BINARY TREES

Validate Binary Search Tree in Java: Range Bounds in O(n)

Practice O(n) Read the full explanation

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

A binary tree is a valid BST when, for every node:

  • every value in its left subtree is strictly smaller than the node's value,
  • every value in its right subtree is strictly larger than the node's value.

The rule is about the whole subtree, not only the children: a node's value must also fit every node above it.

The trees below are written in level order (top to bottom, left to right), with null for a missing child.

 

Example 1:

     2
    / \
   1   3

Input: root = [2,1,3]
Output: true
Explanation: 1 is smaller than 2 and 3 is larger.

Example 2:

     5
    / \
   1   4
      / \
     3   6

Input: root = [5,1,4,null,null,3,6]
Output: false
Explanation: The right child of the root 5 is 4, which is smaller than 5.

Example 3:

     5
    / \
   4   6
      / \
     3   7

Input: root = [5,4,6,null,null,3,7]
Output: false
Explanation: Every node is in order with its own children: 4 < 5 < 6 and 3 < 6 < 7. But 3 is in the right subtree of 5, where every value must be larger than 5. Comparing each node only with its children misses this: each node must lie in the range that all of its ancestors set.

 

Constraints:

  • The number of nodes in the tree is in the range [1, 104].
  • Each node value is between -231 and 231 - 1.
  • The tree is at most 800 levels deep.

Note:

Your algorithm should run in O(n) time, where n is the number of nodes, visiting each node once.

Node values can be the smallest and the largest 32-bit integers, so a bound that means "no limit yet" can't be one of those values.

STEP 1 OF 5
    Ctrl + Enter to check

    JAVA · BINARY TREES

    Validate Binary Search Tree in Java, explained

    Problem overview

    The method isValidBST receives the root of a binary tree and returns true if it is a valid binary search tree. For every node, all values in its left subtree must be strictly smaller than the node's value, and all values in its right subtree must be strictly larger. The rule covers whole subtrees, not just the two children. The lesson provides the TreeNode class, with an int val, a TreeNode left, a TreeNode right and a constructor TreeNode(int x).

    The tree [2, 1, 3] is valid. The tree [5, 1, 4, null, null, 3, 6] is not, because the right child of 5 is 4, which is smaller. The tree [5, 4, 6, null, null, 3, 7] is not valid either, and it is the instructive one: every node is in order with its own children (4 < 5 < 6 and 3 < 6 < 7), but 3 sits in the right subtree of 5, where every value must be larger than 5.

    Approach: pass a range down the tree

    The natural first idea is to compare each node with its two children. That check accepts [5, 4, 6, null, null, 3, 7], because it never compares 3 with 5. In the lesson's runs, such a children-only check returned true for that tree and also for [10, 5, 15, null, null, 6, 20], where 6 lies in the right subtree of 10. Both trees are invalid.

    The fix is to give each node the range that all of its ancestors allow. The root can hold any value, so it starts with no limit. Going into the left child, the node's own value becomes the new upper limit, because everything on the left must be smaller. Going into the right child, the node's value becomes the new lower limit. A node is valid when its value lies strictly between the two limits and both of its subtrees are valid with their narrowed ranges. Each node is visited once, so the time is O(n).

    The limits need a "no limit yet" value, and here that is the tricky part. Node values can be the smallest or the largest int. A limit stored as Integer.MIN_VALUE would reject a legitimate node holding exactly that value, since the comparison is strict. The solution therefore keeps its limits in long variables, starting at Long.MIN_VALUE and Long.MAX_VALUE, which are beyond every int.

    Step by step

    isValidBST

    1. Call the helper validate on root with the widest range, Long.MIN_VALUE and Long.MAX_VALUE, and return its answer. Nothing above the root limits it.

    validate

    1. Declare it to return boolean, taking a TreeNode node and two long bounds, low and high. It answers whether every value in the tree under node is larger than low and smaller than high.
    2. Return true if node is null. An empty tree breaks no rule, and this stops the recursion past a leaf.
    3. Return false if node.val <= low || node.val >= high. The comparison is strict, so a value equal to a bound is also invalid. Java widens the int to a long for the comparison.
    4. Check both subtrees with &&. The left one keeps low and gets node.val as its upper bound. The right one gets node.val as its lower bound and keeps high.

    The solution in Java

    class Solution {
        boolean validate(TreeNode node, long low, long high) {
            if (node == null) {
                return true;
            }
            if (node.val <= low || node.val >= high) {
                return false;
            }
            return validate(node.left, low, node.val) && validate(node.right, node.val, high);
        }
    
        public boolean isValidBST(TreeNode root) {
            return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
        }
    }
    

    Java details worth noticing:

    • Widening conversions. node.val is an int, and passing it where a long is expected is allowed without a cast. The same happens in the comparisons node.val <= low.
    • Why long bounds. With int bounds starting at Integer.MIN_VALUE and Integer.MAX_VALUE, the single-node trees [-2147483648] and [2147483647] were both wrongly rejected, and so was [0, -2147483648, 2147483647]. With the long bounds all three are accepted, which is correct.
    • Short-circuit &&. If the left subtree is invalid, the right one is not examined at all.
    • Recursion depth. The tree is at most 800 levels deep, which the default Java stack handles. A right-leaning chain of 800 nodes with increasing values returns true.

    Complexity

    Time: O(n). Each node is visited once, and each visit does a constant amount of work. Visiting stops early at the first invalid node.

    Space: O(h), where h is the height of the tree, for the recursion stack. It is at most 800 here, and O(log n) for a balanced tree.

    Edge cases and common mistakes

    • Comparing only with children. It passes [5, 4, 6, null, null, 3, 7] and [10, 5, 15, null, null, 6, 20], which are invalid.
    • Duplicates. Equal values are not allowed on either side. [2, 2, 2] and [1, 1] both give false. Using < and > in the rejection test instead of <= and >= would accept them.
    • Extreme values. [-2147483648, null, 2147483647] is valid. This is the reason for long bounds.
    • A single node. [1] is valid whatever its value, because both bounds are far outside the int range.
    • Passing the wrong bound. The left subtree narrows high, and the right subtree narrows low. Swapping them rejects valid trees such as [2, 1, 3].
    • Narrowing both bounds at once. Each child changes only one limit and inherits the other from higher up. That inheritance is what lets a deep node feel the influence of the root.
    • Skewed trees. [3, null, 4, null, 5] is valid: a chain is fine as long as the values keep increasing to the right.

    Testing

    • [2, 1, 3] gives true.
    • [5, 1, 4, null, null, 3, 6] gives false.
    • [5, 4, 6, null, null, 3, 7] gives false.
    • [2, 2, 2] gives false.
    • [-2147483648] gives true.
    • [0, -2147483648, 2147483647] gives true.
    • [10, 5, 15, null, null, 6, 20] gives false.
    • [5, 3, 7, 2, 4, 6, 8] gives true.
    Keep going with Java500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free