Problem Statement
Given a binary tree, we have to determine if the tree is a binary search tree or not.
A tree is said to be a valid binary search tree when,
- All the nodes in the left subtree are less than the parent node
- All the nodes in the right subtree are greater than the parent node.
- Also, the left and right subtrees have to satisfy the binary search tree conditions.
Example
consider the following examples
example-1
3
/ \
2 5
/ \ \
1 4 6
output:
Not a binary search tree
example-2
4
/ \
2 5
/ \ \
1 3 6
output:
Valid binary search tree
Example Explanation
In example 1,
Condition 1 is not satisfied. All the nodes in the left subtree have to be less than the parent node, but 4
is not less than 3
. Hence, it’s not a binary search tree
In example 2,
Condition 1 is satisfied, where all the nodes in the left subtree are less than the parent node. Similarly, condition 2 is satisfied, where all the nodes in the right subtree are greater than the parent node. And condition 3 is satisfied, where both left and right subtrees have fulfilled the bst conditions.
Constraints
The constraints we are going to follow for the below implementations are,
- Given tree can have any number of edges between $0$ and $10000$ (both inclusive).
- Each node can have any value between $-2^{31}$ and $2^{31}-1$ (both inclusive).
Approach 1- Brute Force Approach
The idea is to check if all the nodes in the left subtree are less than the parent node and if all the nodes in the right subtree are greater than the parent node for all the nodes in the tree.
Now instead of checking for all nodes in the left and right subtrees, we can compare the parent node with max in the left subtree, and min in the right subtree.
Algorithm
The algorithm to check for the bst follows as
for each node in the tree,
- Find the max of all the nodes in the left subtree, and check if the max is less than the current node.
- Find the min of all the nodes in the right subtree, and check if the min is greater than the current node.
- Validate if both left and right subtrees satisfy the bst conditions.
Code Implementation
The java implementation of brute force approach to check for bst follows as
class Tree{
//contents of each tree node
int val;
Tree left;
Tree right;
//tree class constructor
Tree(int val){
this.val = val;
left=null;
right=null;
}
}
public class Main{
//method to check if tree is bst
public boolean isBst(Tree root){
//base case
if(root == null)
return true;
//fetch the max of all nodes in the left subtree
int max = getMax(root.left);
/*
condition 1, check if all nodes in left subtree
are less than the parent node
*/
if(max >= root.val)
return false;
//fetch the min of all nodes in the right subtree
int min = getMin(root.right);
/*
condition 2, check if all nodes in right subtree
are greater than the parent node
*/
if(min <= root.val)
return false;
/*
condition 3, check if both the left and right subtree
satisfy the bst conditions
*/
if((!isBst(root.left)) || (!isBst(root.right)))
return false;
return true;
}
public int getMax(Tree root){//method to fetch the max of given tree
//base case
if(root == null)
return Integer.MIN_VALUE;
int leftMax = getMax(root.left);
int rightMax = getMax(root.right);
return Math.max(root.val, Math.max(leftMax, rightMax));
}
public int getMin(Tree root){//method to fetch the min of given tree
//base case
if(root == null)
return Integer.MAX_VALUE;
int leftMin = getMin(root.left);
int rightMin = getMin(root.right);
return Math.min(root.val, Math.min(leftMin, rightMin));
}
public static void main(String[] args) {
Tree root = new Tree(4);
root.left = new Tree(2);
root.right = new Tree(5);
root.left.left = new Tree(1);
root.left.right = new Tree(3);
root.right.right = new Tree(6);
Main m = new Main();
if(m.isBst(root)){
System.out.println("Given tree is a valid bst");
}
else{
System.out.println("Given tree is not a bst");
}
}
}
Output:
Given tree is a valid bst
C++ implementation of brute force approach to check for bst follows as
#include<iostream>
#include<climits>
using namespace std;
struct Tree{
//contents of each tree node
int val;
Tree* left;
Tree* right;
};
//method to initialize new tree node
Tree* newNode(int val){
Tree* node = new Tree;
node->val = val;
node->left = NULL;
node->right = NULL;
return node;
}
int getMax(Tree* root){//method to fetch the max of given tree
//base case
if(root == NULL)
return INT_MIN;
int leftMax = getMax(root->left);
int rightMax = getMax(root->right);
int res = leftMax>rightMax?leftMax:rightMax;
return res>root->val?res:root->val;
}
int getMin(Tree* root){//method to fetch the min of given tree
//base case
if(root == NULL)
return INT_MAX;
int leftMin = getMin(root->left);
int rightMin = getMin(root->right);
int res = leftMin<rightMin?leftMin:rightMin;
return res<root->val?res:root->val;
}
bool isBst(Tree* root){
//base case
if(root == NULL)
return true;
//fetch the max of all nodes in the left subtree
int max = getMax(root->left);
/*
condition 1, check if all nodes in left subtree
are less than the parent node
*/
if(max >= root->val)
return false;
//fetch the min of all nodes in the right subtree
int min = getMin(root->right);
/*
condition 2, check if all nodes in right subtree
are greater than the parent node
*/
if(min <= root->val)
return false;
/*
condition 3, check if both the left and right subtree
satisfy the bst conditions
*/
if((!isBst(root->left)) || (!isBst(root->right)))
return false;
return true;
}
int main() {
Tree* root = newNode(3);
root->left = newNode(2);
root->right = newNode(5);
root->left->left = newNode(1);
root->left->right = newNode(4);
root->right->right = newNode(6);
if(isBst(root)){
cout<<"Given tree is a valid bst";
}
else{
cout<<"Given tree is not a bst";
}
}
Output:
Given tree is not a bst
Python implementation of brute force approach to check for bst follows as
import sys
#contents of a tree node
class Tree:
def __init__(self, val):
self.left = None
self.right = None
self.val = val
def getMax(root):#method to fetch the max of given tree
#base case
if(root == None):
return -sys.maxsize-1
leftMax = getMax(root.left)
rightMax = getMax(root.right)
return max(root.val,max(leftMax,rightMax))
def getMin(root):#method to fetch the min of given tree
#base case
if(root == None):
return sys.maxsize
leftMin = getMin(root.left)
rightMin = getMin(root.right)
return min(root.val, min(leftMin, rightMin))
def isBst(root):
#base case
if(root == None):
return True
#fetch the max of all nodes in the left subtree
max = getMax(root.left)
'''
condition 1, check if all nodes in left subtree
are less than the parent node
'''
if(max >= root.val):
return False
#fetch the min of all nodes in the right subtree
min = getMin(root.right)
'''
condition 2, check if all nodes in right subtree
are greater than the parent node
'''
if(min <= root.val):
return False
'''
condition 3, check if both the left and right subtree
satisfy the bst conditions
'''
if((not isBst(root.left)) or (not isBst(root.right))):
return False
return True
#Driver code
root = Tree(3);
root.left = Tree(2);
root.right = Tree(5);
root.left.left = Tree(1);
root.left.right = Tree(4);
root.right.right = Tree(6);
if(isBst(root)):
print("Given tree is a valid bst")
else:
print("Given tree is not a bst")
Output:
Given tree is not a bst
Time Complexity
For each node, we are fetching the maximum of the left subtree and the minimum of the right subtree. The time required to find the minimum or maximum of a tree is O(N). Since we are doing it for each node and fetching both minimum and maximum, the time complexity will be N*O(N+N) i.e., O(N^2)
.
Space Complexity
This approach doesn’t use any auxiliary space, but it internally maintains a recursion stack of size H(height of tree) for checking bst and getting min functions. So the space complexity will be O(H)
.
Approach 2 – Using Upper and Lower Limits
The idea is to remove the getMin
and getMax
methods by adding lower and upper limit parameters in the function and check if the current node falls under the expected range. And one of the boundaries of the range gets narrowed in the further recursive calls to validate if the left and right subtrees satisfy the bst conditions.
Algorithm
The algorithm for the recursive approach follows as
- Add the parameters
left
andright
along withroot
in the function. - Check if the current node value falls between
left
andright
. - Traverse to left subtree by updating the right boundary to current node value.
- Traverse to right subtree by updating the left boundary to current node value.
Implementation
The java implementation for the above approach to check for bst follows as
class Tree{
//contents of each tree node
int val;
Tree left;
Tree right;
//tree class constructor
Tree(int val){
this.val = val;
left=null;
right=null;
}
}
public class Main{
//method to check if tree is bst
public boolean IsBst(Tree root, int minValue, int maxValue){
//base case
if(root == null)
return true;
//check if current value is greater than left boundary
if(root.val<=minValue)
return false;
//check if currernt value is less right boundary
if(root.val>=maxValue)
return false;
/*
traverse to left subtree by narrowing the right boundary
and traverse to right subtree by narrowing the left boundary
*/
if(!IsBst(root.left, minValue, root.val) || !IsBst(root.right, root.val, maxValue))
return false;
return true;
}
public static void main(String[] args) {
Tree root = new Tree(4);
root.left = new Tree(2);
root.right = new Tree(5);
root.left.left = new Tree(1);
root.left.right = new Tree(3);
root.right.right = new Tree(6);
Main m = new Main();
if(m.IsBst(root, Integer.MIN_VALUE, Integer.MAX_VALUE)){
System.out.println("Given tree is a valid bst");
}
else{
System.out.println("Given tree is not a bst");
}
}
}
C++ implementation for the above approach to check for bst follows as
#include<iostream>
#include<climits>
using namespace std;
struct Tree{
//contents of each tree node
int val;
Tree* left;
Tree* right;
};
//method to initialize new tree node
Tree* newNode(int val){
Tree* node = new Tree;
node->val = val;
node->left = NULL;
node->right = NULL;
return node;
}
//method to check if tree is bst
bool IsBst(Tree* root, int minValue, int maxValue){
//base case
if(root == NULL)
return true;
//check if current value is greater than left boundary
if(root->val<=minValue)
return false;
//check if currernt value is less than right boundary
if(root->val>=maxValue)
return false;
/*
traverse to left subtree by narrowing the right boundary
and traverse to right subtree by narrowing the left boundary
*/
if(!IsBst(root->left, minValue, root->val) || !IsBst(root->right, root->val, maxValue))
return false;
return true;
}
int main() {
Tree* root = newNode(4);
root->left = newNode(2);
root->right = newNode(5);
root->left->left = newNode(1);
root->left->right = newNode(3);
root->right->right = newNode(6);
if(IsBst(root, INT_MIN, INT_MAX)){
cout<<"Given tree is a valid bst";
}
else{
cout<<"Given tree is not a bst";
}
}
Python implementation for the above approach to check for bst follows as
import sys
#contents of a tree node
class Tree:
def __init__(self, val):
self.left = None
self.right = None
self.val = val
#method to check if tree is bst
def IsBst(root, minValue, maxValue):
#base case
if(root == None):
return True
#check if current value is greater than left boundary
if(root.val<=minValue):
return False
#check if currernt value is less than right boundary
if(root.val>=maxValue):
return False
'''
traverse to left subtree by narrowing the right boundary
and traverse to right subtree by narrowing the left boundary
'''
if(not IsBst(root.left, minValue, root.val) or not IsBst(root.right, root.val, maxValue)):
return False
return True
#Driver code
root = Tree(4);
root.left = Tree(2);
root.right = Tree(5);
root.left.left = Tree(1);
root.left.right = Tree(3);
root.right.right = Tree(6);
if(IsBst(root, -sys.maxsize-1, sys.maxsize)):
print("Given tree is a valid bst")
else:
print("Given tree is not a bst")
Output:
Given tree is a valid bst
Time Complexity
This approach has to visit each node in the tree and validate if the value falls under the expected range. So the time complexity will be O(N)
.
Space Complexity
This approach doesn’t require any auxiliary space, but it internally maintains a recursion stack of size H(height of binary tree). So the space complexity will be O(H)
.
Approach 3 – Using Inorder Traversal
One of the interesting properties of a binary search tree is that if we flatten the tree or consider the inorder traversal of the tree, the series will be in ascending order. Now one of the ways is to perform inorder traversal and check if the obtained array is sorted or not. But we can validate this during the inorder traversal itself.
The idea is to maintain a prev pointer and check if the next node in the inorder traversal is greater than the prev node or not.
Algorithm
The algorithm for the recursive inorder approach follows as
- Maintain a
prev
node and initialize it with NULL. - Perform a recursive inorder traversal.
- If
prev
is NULL, update it to current. - If
prev
is not NULL, validate if the current node is larger thanprev
. - Update the
prev
to current.
Similarly, the algorithm for the iterative inorder approach follows as
- Maintain a
prev
node, initialize it with NULL and maintain a stack for inorder traversal. - Perform an iterative inorder traversal.
- If
prev
is NULL, update it to current. - If
prev
is not NULL, validate if the current node is larger thanprev
. - Update the
prev
to current.
Implementation
The java implementation for the inorder approach to check for bst follows as
import java.util.*;
class Tree{
//contents of each tree node
int val;
Tree left;
Tree right;
//tree class constructor
Tree(int val){
this.val = val;
left=null;
right=null;
}
}
public class Main{
Tree prev;
//modified inorder traversal,
//which compares current node with previous nodes
public boolean modifiedInorder(Tree root){
//base case
if(root == null)
return true;
//traverse the left subtree
if(!modifiedInorder(root.left))
return false;
//if prev is null update it to root
if(prev == null)
prev=root;
/*
else compare the current with prev
and update the prev to current
*/
else if(root.val<=prev.val)
return false;
else
prev=root;
//traverse the right subtree
return modifiedInorder(root.right);
}
//method to check if tree is bst
public boolean recursiveIsBst(Tree root){
prev = null;
return modifiedInorder(root);
}
public boolean iterativeIsBst(Tree root){
Stack<Tree> s=new Stack<>();
Tree previous = null;
Tree curr = root;
//modified iterative inorder traversal
while(!s.isEmpty() || curr!=null){
//traverse the left subtree
if(curr!=null){
s.push(curr);
curr=curr.left;
continue;
}
curr = s.peek();
s.pop();
/*
if prev is null update it to curr
else compare the current with prev
and update the prev to current
*/
if(previous!=null && curr.val<=previous.val)
return false;
previous = curr;
//traverse the right subtree
curr=curr.right;
}
return true;
}
public static void main(String[] args) {
Tree root = new Tree(4);
root.left = new Tree(2);
root.right = new Tree(5);
root.left.left = new Tree(1);
root.left.right = new Tree(3);
root.right.right = new Tree(6);
Main m = new Main();
if(m.recursiveIsBst(root)){
System.out.println("Given tree is a valid bst");
}
else{
System.out.println("Given tree is not a bst");
}
if(m.iterativeIsBst(root)){
System.out.println("Given tree is a valid bst");
}
else{
System.out.println("Given tree is not a bst");
}
}
}
Output:
Given tree is a valid bst
Given tree is a valid bst
C++ implementation for the inorder approach to check for bst follows as
#include<iostream>
#include <stack>
using namespace std;
struct Tree{
//contents of each tree node
int val;
Tree* left;
Tree* right;
};
//method to initialize new tree node
Tree* newNode(int val){
Tree* node = new Tree;
node->val = val;
node->left = NULL;
node->right = NULL;
return node;
}
//method to check if tree is bst
//modified inorder traversal,
//which compares current node with previous nodes
bool recursiveIsBst(Tree* root){
static Tree* prev = NULL;
//base case
if(root == NULL)
return true;
//traverse the left subtree
if(!recursiveIsBst(root->left))
return false;
//if prev is null update it to root
if(prev == NULL)
prev=root;
/*
else compare the current with prev
and update the prev to current
*/
else if(root->val<=prev->val)
return false;
else
prev=root;
//traverse the right subtree
return recursiveIsBst(root->right);
}
//iterative approach to check if tree is bst
bool iterativeIsBst(Tree* root){
stack<Tree*> s;
Tree* previous = NULL;
Tree* curr = root;
//modified iterative inorder traversal
while(!s.empty() || curr!=NULL){
//traverse the left subtree
if(curr!=NULL){
s.push(curr);
curr=curr->left;
continue;
}
curr = s.top();
s.pop();
/*
if prev is null update it to curr
else compare the current with prev
and update the prev to current
*/
if(previous!=NULL && curr->val<=previous->val)
return false;
previous = curr;
//traverse the right subtree
curr=curr->right;
}
return true;
}
int main() {
Tree* root = newNode(3);
root->left = newNode(2);
root->right = newNode(5);
root->left->left = newNode(1);
root->left->right = newNode(4);
root->right->right = newNode(6);
if(recursiveIsBst(root)){
cout<<"Given tree is a valid bst \n";
}
else{
cout<<"Given tree is not a bst \n";
}
if(iterativeIsBst(root)){
cout<<"Given tree is a valid bst";
}
else{
cout<<"Given tree is not a bst";
}
}
Output:
Given tree is not a bst
Given tree is not a bst
Python implementation for the inorder approach to check for bst follows as
#contents of a tree node
class Tree:
def __init__(self, val):
self.left = None
self.right = None
self.val = val
prev=None
#modified inorder traversal,
#which compares current node with previous nodes
def modifiedInorder(root):
global prev
#base case
if(root == None):
return True
#traverse the left subtree
if(not modifiedInorder(root.left)):
return False
'''
if prev is null update it to root
else compare the current with prev
and update the prev to current
'''
if(prev == None):
prev=root
elif(root.val<=prev.val):
return False
else:
prev=root
#traverse the right subtree
return modifiedInorder(root.right)
#recurisve method to check if tree is bst
def recursiveIsBst(root):
global prev
prev = None
return modifiedInorder(root)
#iterative method to check if tree is bst
def iterativeIsBst(root):
stack = []
previous = None
curr = root
#modified iterative inorder traversal
while(stack or curr!=None):
#traverse the left subtree
if(curr!=None):
stack.append(curr)
curr=curr.left
continue
curr = stack.pop()
'''
if prev is null update it to curr
else compare the current with prev
and update the prev to current
'''
if(previous!=None and curr.val<=previous.val):
return False
previous = curr
#traverse the right subtree
curr=curr.right
return True
#Driver code
root = Tree(3);
root.left = Tree(2);
root.right = Tree(5);
root.left.left = Tree(1);
root.left.right = Tree(4);
root.right.right = Tree(6);
if(recursiveIsBst(root)):
print("Given tree is a valid bst")
else:
print("Given tree is not a bst")
if(iterativeIsBst(root)):
print("Given tree is a valid bst")
else:
print("Given tree is not a bst")
Output:
Given tree is not a bst
Given tree is not a bst
Time Complexity
In the recursive approach, we are visiting each node in the tree and compare it with the previous node. So the time complexity will be O(N)
.
Similarly, in the iterative approach, we are looping over the stack of size N, so the time complexity will be O(N)
.
Space Complexity
The recursive approach doesn’t require any auxiliary space, but it internally maintains a recursion stack of size H(height of tree) for inorder traversal. So the space complexity will be O(H)
.
The iterative approach uses an auxiliary stack for inorder traversal that at most go to size H(height of tree), so the space complexity will be O(H)
.
Conclusion
- A tree is a valid binary search tree when all the nodes in the left subtree are less than and all the nodes in the right subtree are larger than the parent node.
- Each subtree in a bst must also satisfy the bst conditions.
- The brute force approach to check for a bst is by comparing the current node with the max of the left subtree and the min of the right subtree. Time complexity is
O(N^2)
, and space complexity isO(H)
. - A little optimized approach is to set boundaries for a node value and validate if each node satisfies the boundaries. Time complexity is
O(N)
, and space complexity isO(H)
. - We can use inorder traversal to check if a tree is bst by maintaining a previous pointer and comparing it with the current. Time Complexity is
O(N)
, and space complexity isO(H)
. - Inorder approach to check for a BST is the best in terms of time and space.