Write a method, isBST
, that checks if a given Binary Tree is a Binary Search Tree.
Example:
13
/ \
8 18
isBST(Root 13)
Output: True
13
/ \
8 18
/ \
6 14
isBST(Root 13)
Output: False
5
isBST(Root 5)
Output: True
isBST(NULL)
Output: True
Keep in mind, an empty Binary Search Tree is still a valid Binary Search Tree. isBST(null)
should return true
.
A very common solution to this algorithm is to write a method that, for each node, checks if the right node is greater and the left node is smaller. That might look something like this:
function Node(value, left = null, right = null) {
this.value = value;
this.left = left;
this.right = right;
}
function isBST(root) {
if (root === null) return true;
if (root.left.value > root.value || root.right.value < root.value)
return false;
return isBST(root.left) && isBST(root.right);
}
If your partner starts with this intuition, let them follow it. Ask them if they see anything wrong with this approach. If they still can't see it, draw out an example of an invalid tree that would pass as valid (see below).
At first, this makes sense intuitively. The issue is that this method would return true for the following Binary Tree:
13
/ \
8 18
/ \
6 14
The 14
makes this tree invalid, as it should be to the right of the 13
.
How can we add this logic to our program?
For each node, check if the max value in the left subtree is smaller than the node and the min value in the right subtree is greater than the node.
function isBST(node) {
if (node === null) return true;
if (node.left && maxValue(node.left) > node.value)
return false;
if (node.right && minValue(node.right) < node.value)
return false;
return isBST(node.left) && isBST(node.right);
}
This method assumes that we have two helper functions, maxValue
and minValue
, that return the maximum and minimum values in the subtree of a given node.
While this method would work, it is not the most efficient solution. Calling maxValue
and minValue
for every single node makes this implementation O(n * log(n))
.
How can we do better?
function isBST(node, minValue, maxValue) {
if (node === null) return true;
if (minValue && node.value < minValue)
return false;
if (maxValue && node.value > maxValue)
return false;
return isBST(node.left, minValue, node.value) &&
isBST(node.right, node.value, maxValue);
}
Explanation:
In the Brute Force solution, we kept finding the max and min value for each node. What if this was something we stored and passed down? Consider the following tree:
13
/ \
8 18
/ \
6 14
We can check if it is valid by following these steps:
- Start at the root (
13
).minValue
andmaxValue
are undefined in our first call to the method, so we don't run the comparison. Because we don't have these values, we make the recursive call to see if both the left and right subtrees are valid- When we make our recursive call to the left, we pass our own value (
13
) down as the max value we have seen so far. That is because, if we are searching the left tree, we know we want every single node to have a value smaller than13
- Vice verse for the right subtree. Pass
13
down as the minimum value we have seen. We know that there cannot be any nodes smaller than 13 in the right subtree.
- When we make our recursive call to the left, we pass our own value (
- Once we get to the node
14
, themaxValue
will point to13
, and theminValue
will point to8
. In our comparison check, this call will returnfalse
because14
is greater than themaxValue
, making this tree invalid.