98. Validate Binary Search Tree
Problem Description
The problem asks to verify whether a given binary tree is a valid binary search tree (BST). By definition, a valid BST is characterized by the following properties:
- Every node's left subtree contains only nodes with keys that are less than the node's own key.
- Every node's right subtree contains only nodes with keys that are greater than the node's own key.
- Both the left and right subtrees must also themselves be valid BSTs.
The objective is to use these criteria to check every node in the tree and ensure that the structure adheres to the rules of a BST.
Flowchart Walkthrough
To analyze the LeetCode problem 98, "Validate Binary Search Tree," let's use the Flowchart, which can be viewed here. We'll perform a step-by-step assessment to determine the appropriate algorithm:
Is it a graph?
- Yes: A binary search tree (BST) is essentially a special kind of graph, represented by nodes and edges where each node has at most two children.
Is it a tree?
- Yes: Definitely, since a binary search tree is a type of tree.
DFS
- At this point, since the structure in question is a tree, Depth-First Search (DFS) becomes a suitable approach for traversing or validating the tree. DFS is particularly beneficial for trees as it allows us to examine all nodes in a particular path before retracing steps, which is especially useful in recursive checks for properties like those required in a BST validation.
Conclusion: Using the DFS pattern is appropriate for this task because it lets us compare each node against its sub-tree limits effectively, verifying the BST property efficiently as we traverse the tree depth-first.
This analysis, using the determined flow chart steps, clearly indicates that DFS is an effective method to tackle this particular problem.
Intuition
The solution uses the concept of in-order traversal. In a BST, an in-order traversal yields the nodes' values in ascending order. The approach involves a Depth-First Search (DFS) where we traverse the tree in an in-order manner (left, node, right), keeping track of the value of the previously visited node (prev
). During the traversal, we ensure that each subsequent node has a greater value than the previous node.
- A recursive function
dfs
is defined, which will do an in-order traversal of the tree. - If we encounter a
None
(indicative of reaching the end of a path), we returnTrue
, because an empty tree is technically a valid BST. - As we traverse the left subtree, we check for violations of the BST properties.
- Before visiting the current node, we ensure that we've finished validating the left subtree. If the left subtree is invalid, the function returns
False
. - We then check the current node's value against
prev
(the previous node's value, initialized to negative infinity). The value must be strictly greater to satisfy BST conditions. - Update
prev
to the current node's value. - Proceed to validate the right subtree.
- If every recursive call returns
True
, the entire tree is a valid BST, and thus the function returnsTrue
.
This approach ensures that we confirm the BST property where each node's value must be greater than all values in its left subtree and less than all values in its right subtree.
Learn more about Tree, Depth-First Search, Binary Search Tree and Binary Tree patterns.
Solution Approach
The provided Python code implements the DFS in-order traversal to check the validity of the BST, and it uses the TreeNode
data structure, which is a standard representation of a node in a binary tree, consisting of a value val
and pointers to left
and right
child nodes.
Here is the step-by-step breakdown of the solution approach:
-
In-order traversal (DFS): We recursively traverse the binary tree using in-order traversal, where we first visit the left subtree, then the node, and finally the right subtree.
-
Global variable: A global variable
prev
(initialized to negative infinity) is used to keep track of the value of the last visited node in the traversal. -
Checking BST properties:
- When visiting a node, we first call the
dfs
function on its left child. If this function call returnsFalse
, it means the left subtree contains a violation of the BST rules, and thus, we returnFalse
immediately to stop checking further. - After checking the left subtree, we examine the current node by comparing its value with
prev
. If the current node's value is less than or equal toprev
, this means the in-order traversal sequence is broken, and hence, the tree is not a valid BST. We returnFalse
in this case. - The
prev
variable is then updated to the current node's value, indicating that this node has been processed, and we move to the right subtree.
- When visiting a node, we first call the
-
Recursive base case: For a
None
node, which signifies the end of a branch, thedfs
function returnsTrue
, as an empty tree or subtree is valid by definition. -
Final validation: After the entire tree has been traversed, if no violations were found, the initial call to
dfs(root)
will ultimately returnTrue
, confirming the tree is a valid BST.
The key algorithmic pattern used here is recursion, combined with the properties of in-order traversal for a BST. The recursive function dfs
combines the logic for traversal and validation, effectively checking the BST properties as it moves through the tree.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small binary tree with the following structure to illustrate the solution approach:
2 / \ 1 3
Our target is to walk through the solution approach provided above and confirm whether this tree is a valid BST.
-
Step 1 - In-order traversal (DFS): We begin the depth-first search (DFS) with an in-order traversal from the root node which is 2. The in-order traversal dictates that we visit the left subtree first, then the root node, and finally the right subtree.
-
Step 2 - Global variable: Initially, the
prev
variable is set to negative infinity. It will help us to keep track of the last visited node's value. -
Step 3 - Checking BST properties:
- We start with the left child (node 1). Since node 1 has no children, we compare it to
prev
, which is negative infinity. Node 1 is greater, so we updateprev
to 1, and then we return back to node 2. - Now, we are at node 2 and compare its value with
prev
which is now 1. The value of node 2 is greater; therefore, it satisfies the BST condition. We updateprev
to 2 and proceed to the right subtree. - In the right subtree, we have node 3. We call the
dfs
function on node 3. As it has no children, we compare it toprev
(which is now 2). Node 3 is greater than 2, so we updateprev
to 3.
- We start with the left child (node 1). Since node 1 has no children, we compare it to
-
Step 4 - Recursive base case: Since we reached the leaf nodes without encountering any
None
nodes, we confirm that all subtrees are valid BSTs as well. -
Step 5 - Final validation: After visiting all the nodes following the in-order traversal, and none of the recursive
dfs
calls returnedFalse
, the whole tree is thus confirmed to be a valid BST. The final call todfs(root)
returnsTrue
.
By following the in-order traversal method and checking each node against the previous node's value, we have verified that the given tree meets all the properties of a valid BST:
- Each node's left subtree contains only nodes with values less than the node's own value.
- Each node's right subtree contains only nodes with values greater than the node's own value.
- All the subtrees are valid BSTs on their own.
In summary, the example binary tree with nodes 1, 2, and 3 is indeed a valid binary search tree.
Solution Implementation
1class TreeNode:
2 # A binary tree node has a value, and references to left and right child nodes.
3 def __init__(self, val=0, left=None, right=None):
4 self.val = val
5 self.left = left
6 self.right = right
7
8class Solution:
9 def isValidBST(self, root: TreeNode) -> bool:
10 # Helper function to perform an in-order traversal of the tree.
11 def in_order_traversal(node):
12 nonlocal last_value_visited # To keep track of the last value visited in in-order traversal.
13
14 # If the current node is None, it's a leaf, so return True.
15 if node is None:
16 return True
17
18 # Traverse the left subtree.
19 if not in_order_traversal(node.left):
20 return False
21
22 # Check if the previous value in the in-order traversal is greater than or equal to the current node value,
23 # which would violate the BST property.
24 if last_value_visited >= node.val:
25 return False
26
27 # Update the last value visited with the current node's value.
28 last_value_visited = node.val
29
30 # Traverse the right subtree.
31 if not in_order_traversal(node.right):
32 return False
33
34 # No violations found, return True.
35 return True
36
37 # We initialize the last visited value with negative infinity (smallest possible value)
38 # to make sure the first comparison is always True.
39 last_value_visited = float('-inf')
40
41 # Start the in-order traversal of the tree with the root node.
42 return in_order_traversal(root)
43
1/**
2 * Definition for a binary tree node.
3 * This class represents a node of a binary tree which includes value, pointer to left child
4 * and pointer to right child.
5 */
6class TreeNode {
7 int val; // node's value
8 TreeNode left; // pointer to left child
9 TreeNode right; // pointer to right child
10
11 // Default constructor.
12 TreeNode() {}
13
14 // Constructor with node value.
15 TreeNode(int val) {
16 this.val = val;
17 }
18
19 // Constructor with node value, left child, and right child.
20 TreeNode(int val, TreeNode left, TreeNode right) {
21 this.val = val;
22 this.left = left;
23 this.right = right;
24 }
25}
26
27/**
28 * This class contains method to validate if a binary tree is a binary search tree (BST).
29 */
30class Solution {
31 private Integer previousValue; // variable to store the previously visited node's value
32
33 /**
34 * Validates if the given binary tree is a valid binary search tree (BST).
35 *
36 * @param root The root of the binary tree to check.
37 * @return true if the given tree is a BST, false otherwise.
38 */
39 public boolean isValidBST(TreeNode root) {
40 previousValue = null; // Initialize previousValue as null before starting traversal
41 return inOrderTraversal(root);
42 }
43
44 /**
45 * Performs an in-order depth-first traversal on the binary tree to validate BST property.
46 * It ensures that every node's value is greater than the values of all nodes in its left subtree
47 * and less than the values of all nodes in its right subtree.
48 *
49 * @param node The current node being visited in the traversal.
50 * @return true if the subtree rooted at 'node' satisfies BST properties, false otherwise.
51 */
52 private boolean inOrderTraversal(TreeNode node) {
53 if (node == null) {
54 return true; // Base case: An empty tree is a valid BST.
55 }
56 // Traverse the left subtree. If it's not a valid BST, return false immediately.
57 if (!inOrderTraversal(node.left)) {
58 return false;
59 }
60 // Check the current node value with the previous node's value.
61 // As it's an in-order traversal, previousValue should be less than the current node's value.
62 if (previousValue != null && previousValue >= node.val) {
63 return false; // The BST property is violated.
64 }
65 previousValue = node.val; // Update previousValue with the current node's value.
66 // Traverse the right subtree. If it's not a valid BST, return false immediately.
67 if (!inOrderTraversal(node.right)) {
68 return false;
69 }
70 return true; // All checks passed, it's a valid BST.
71 }
72}
73
1#include <climits>
2
3/**
4 * Definition for a binary tree node.
5 */
6struct TreeNode {
7 int val;
8 TreeNode *left;
9 TreeNode *right;
10 // Constructor to initialize a node with a given value,
11 // with left and right pointers set to nullptr by default
12 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
13 // Constructor to create a node with given value, left, and right children
14 TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
15};
16
17class Solution {
18private:
19 // Pointer to store the last visited node in the binary search tree
20 // to compare its value with the current node's value.
21 TreeNode* lastVisited;
22
23 // Helper function to perform an in-order traversal of the tree
24 // and check if it is a valid binary search tree.
25 bool inorderTraversal(TreeNode* node) {
26 // Base case: If the current node is null, then it's valid
27 if (!node) return true;
28
29 // Recursively traverse the left subtree.
30 // If the left subtree is not a valid BST, the entire tree is not.
31 if (!inorderTraversal(node->left)) return false;
32
33 // If the last visited node is not null and the value of the last visited node
34 // is greater than or equal to the current node's value, then it's not a valid BST.
35 if (lastVisited && lastVisited->val >= node->val) return false;
36
37 // Update the last visited node to the current node
38 lastVisited = node;
39
40 // Recursively traverse the right subtree.
41 // If the right subtree is not a valid BST, the entire tree is not.
42 if (!inorderTraversal(node->right)) return false;
43
44 // If both subtrees are valid, return true
45 return true;
46 }
47
48public:
49 // Function to check whether a binary tree is a valid binary search tree.
50 bool isValidBST(TreeNode* root) {
51 // Initialize the last visited node as null before starting the traversal
52 lastVisited = nullptr;
53
54 // Call the helper function to check if the tree is a valid BST
55 return inorderTraversal(root);
56 }
57};
58
1// Define the structure of a TreeNode with TypeScript interface
2interface TreeNode {
3 val: number;
4 left: TreeNode | null;
5 right: TreeNode | null;
6}
7
8/**
9 * Validates if the provided tree is a binary search tree.
10 * @param {TreeNode | null} root - The root node of the binary tree to validate.
11 * @return {boolean} - True if the tree is a valid BST; otherwise, false.
12 */
13const isValidBST = (root: TreeNode | null): boolean => {
14 // Define the previous node to keep track of during in-order traversal
15 let previous: TreeNode | null = null;
16
17 /**
18 * Performs in-order traversal to check each node's value strictly increasing.
19 * @param {TreeNode | null} node - The current node in the traversal.
20 * @return {boolean} - True if the subtree is valid; otherwise, false.
21 */
22 const inorderTraversal = (node: TreeNode | null): boolean => {
23 if (node === null) {
24 return true;
25 }
26
27 // Traverse the left subtree
28 if (!inorderTraversal(node.left)) {
29 return false;
30 }
31
32 // Check if the current node's value is greater than the previous node's value
33 if (previous && node.val <= previous.val) {
34 return false;
35 }
36
37 // Update the previous node to the current node
38 previous = node;
39
40 // Traverse the right subtree
41 return inorderTraversal(node.right);
42 };
43
44 // Start the recursive in-order traversal from the root
45 return inorderTraversal(root);
46};
47
48// Sample usage (in TypeScript you would normally type the input, here it is implicit for brevity)
49// let tree: TreeNode = {
50// val: 2,
51// left: { val: 1, left: null, right: null },
52// right: { val: 3, left: null, right: null }
53// };
54// console.log(isValidBST(tree)); // Outputs: true
55
Time and Space Complexity
The given Python code defines a method isValidBST
to determine if a binary tree is a valid binary search tree. It uses depth-first search (DFS) to traverse the tree.
Time Complexity:
The time complexity of this algorithm is O(n)
, where n
is the number of nodes in the binary tree. This is because the DFS visits each node exactly once to compare its value with the previously visited node's value.
Space Complexity:
The space complexity of this code is O(h)
, where h
is the height of the tree. This is determined by the maximum number of function calls on the call stack at any one time, which occurs when the algorithm is traversing down one side of the tree. In the worst case of a completely unbalanced tree (like a linked list), the space complexity would be O(n)
. For a balanced tree, it would be O(log n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https algomonster s3 us east 2 amazonaws com cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
Want a Structured Path to Master System Design Too? Don’t Miss This!