Search in a Binary Search Tree
EasyPrompt
You are given the root of a binary search tree (BST) and an integer val.
Find the node in the BST that the node's value equals val and return the subtree rooted with that node. If such a node does not exist, return null.
Example 1:
Input: root = [4,2,7,1,3], val = 2
Output: [2,1,3]Example 2:
Input: root = [4,2,7,1,3], val = 5
Output: []
Constraints:
- The number of nodes in the tree is in the range
[1, 5000]. 1 <= Node.val <= 107rootis a binary search tree.1 <= val <= 107
Approaches
3 approaches with complexity analysis and trade-offs.
This is an iterative version of the optimized BST search. It uses a loop instead of recursion to traverse the tree, which eliminates the recursion call stack overhead and reduces space complexity to a constant.
Algorithm
- Initialize a
currentpointer to theroot. - Loop as long as
currentis notnullandcurrent.valis not equal toval. - Inside the loop, if
valis less thancurrent.val, updatecurrenttocurrent.left. - Otherwise (if
valis greater thancurrent.val), updatecurrenttocurrent.right. - After the loop, return the
currentpointer.
Walkthrough
We initialize a pointer, current, to the root of the tree. We then enter a loop that continues as long as current is not null. Inside the loop, we compare val with current.val.
- If
valis equal tocurrent.val, we've found the node and break the loop. - If
valis less thancurrent.val, we know the target must be in the left subtree, so we updatecurrenttocurrent.left. - If
valis greater thancurrent.val, we updatecurrenttocurrent.right. The loop effectively traverses a single path from the root down towards the potential location of the node. When the loop terminates,currentwill either point to the found node or benullif the value doesn't exist in the tree. We then returncurrent.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public TreeNode searchBST(TreeNode root, int val) { TreeNode current = root; while (current != null && current.val != val) { if (val < current.val) { current = current.left; } else { current = current.right; } } return current; }}Complexity
Time
O(H), where H is the height of the tree. Similar to the recursive approach, this is O(log N) for a balanced tree and O(N) for a skewed tree.
Space
O(1). This is the main advantage over the recursive approach. We only use a single pointer to traverse the tree, so the space used is constant regardless of the tree's size or structure.
Trade-offs
Pros
Most efficient in terms of space complexity (O(1)).
Avoids potential stack overflow errors for very deep trees.
Generally faster in practice due to no function call overhead.
Cons
The code might be slightly less intuitive for some developers compared to the direct recursive translation of the search logic.
This approach treats the Binary Search Tree as a regular binary tree and performs a standard traversal (like pre-order) to visit every node. It doesn't leverage the inherent ordering property of a BST.
Algorithm
- If the current node is
null, returnnull. - If the current node's value equals
val, return the current node. - Recursively search in the left subtree. If the node is found there, return the result.
- If not found in the left subtree, recursively search in the right subtree and return its result.
Walkthrough
The algorithm starts at the root and recursively explores the entire tree. At each node, it checks if the node's value matches the target value val. If a match is found, the node is returned immediately. If the current node is not a match, the search continues in the left subtree, and if not found there, it proceeds to the right subtree. If the entire tree is traversed without finding the value, null is returned. This method is exhaustive and guarantees finding the node if it exists, but it's inefficient as it misses the optimization opportunity provided by the BST structure.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public TreeNode searchBST(TreeNode root, int val) { if (root == null) { return null; } if (root.val == val) { return root; } // Search in the left subtree TreeNode leftResult = searchBST(root.left, val); if (leftResult != null) { return leftResult; } // If not in the left, search in the right subtree return searchBST(root.right, val); }}Complexity
Time
O(N), where N is the number of nodes in the tree. In the worst-case scenario, we might have to visit every node to find the value or determine it's not present.
Space
O(H), where H is the height of the tree, due to the recursion call stack. In the worst case of a skewed tree, H can be equal to N, leading to O(N) space complexity.
Trade-offs
Pros
Simple to understand and implement.
Guaranteed to work for any binary tree, not just a BST.
Cons
Inefficient as it does not utilize the properties of a BST.
Visits unnecessary nodes, leading to a slower runtime compared to optimized approaches.
This approach takes full advantage of the Binary Search Tree property: values in the left subtree are smaller, and values in the right subtree are larger than the current node's value. This allows us to eliminate half of the remaining tree at each step.
Algorithm
- If the root is
nullorroot.valis equal toval, return the root. - If
valis less thanroot.val, recursively search in the left subtree:return searchBST(root.left, val). - If
valis greater thanroot.val, recursively search in the right subtree:return searchBST(root.right, val).
Walkthrough
The search starts at the root. We compare the target value val with the current node's value.
- If
valis equal to the node's value, we've found our target and return the node. - If
valis less than the node's value, we know the target, if it exists, must be in the left subtree. So, we recursively call the search function on the left child. - If
valis greater than the node's value, the target must be in the right subtree. We then recursively call the search function on the right child. - If we reach a
nullnode, it means the value is not in the tree, and we returnnull.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public TreeNode searchBST(TreeNode root, int val) { if (root == null || root.val == val) { return root; } if (val < root.val) { return searchBST(root.left, val); } else { return searchBST(root.right, val); } }}Complexity
Time
O(H), where H is the height of the tree. For a balanced BST, H ≈ log(N), making the complexity O(log N). For a skewed tree, H = N, leading to a worst-case complexity of O(N).
Space
O(H) due to the recursion call stack. For a balanced BST, this is O(log N). For a skewed tree, it's O(N).
Trade-offs
Pros
Much more efficient than a brute-force search for balanced trees.
The logic is clean and directly reflects the definition of a BST search.
Cons
The space complexity can be O(N) in the worst case of a skewed tree.
Recursive calls can have a slight overhead compared to an iterative solution.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public TreeNode searchBST ( TreeNode root , int val ) { if ( root == null || root . val == val ) { return root ; } return root . val < val ? searchBST ( root . right , val ) : searchBST ( root . left , val ); } }# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution : def searchBST ( self , root : TreeNode , val : int ) -> TreeNode : if root is None or root . val == val : return root return ( self . searchBST ( root . right , val ) if root . val < val else self . searchBST ( root . left , val ) )/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: TreeNode * searchBST ( TreeNode * root , int val ) { if ( ! root || root -> val == val ) return root ; return root -> val < val ? searchBST ( root -> right , val ) : searchBST ( root -> left , val ); } };Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.