BST Floor and Ceiling
Problem
Given a binary search tree, find the floor and ceiling of a given integer. The floor is the highest element in the tree less than or equal to an integer, while the ceiling is the lowest element in the tree greater than or equal to an integer.
If either value does not exist, return None.
Examples
Example 1
Input:
BST: 10
/ \
5 15
/ \ / \
2 7 12 20
target = 6
Output: floor = 5, ceiling = 7
Explanation: 5 is the highest value ≤ 6, and 7 is the lowest value ≥ 6.
Example 2
Input:
BST: 10
/ \
5 15
/ \ / \
2 7 12 20
target = 15
Output: floor = 15, ceiling = 15
Explanation: 15 exists in the tree, so both floor and ceiling are 15.
Example 3
Input:
BST: 10
/ \
5 15
/ \ / \
2 7 12 20
target = 1
Output: floor = None, ceiling = 2
Explanation: No value ≤ 1 exists, but 2 is the smallest value ≥ 1.
Example 4
Input:
BST: 10
/ \
5 15
/ \ / \
2 7 12 20
target = 25
Output: floor = 20, ceiling = None
Explanation: 20 is the highest value ≤ 25, but no value ≥ 25 exists.
Example 5
Input:
BST: empty tree
target = 10
Output: floor = None, ceiling = None
Explanation: Empty tree has no floor or ceiling values.
Solution
Method 1 - Iterative BST Traversal
Intuition
We can use the BST property to efficiently find floor and ceiling. For each node, if the value equals target, both floor and ceiling are found. If value is less than target, it's a potential floor candidate and we search right. If value is greater than target, it's a potential ceiling candidate and we search left.
Approach
- Initialize floor and ceiling as None
- Start from root and traverse the BST
- At each node:
- If node value equals target: both floor and ceiling are found
- If node value < target: update floor candidate and go right
- If node value > target: update ceiling candidate and go left
- Continue until we reach a leaf or find exact match
- Return the final floor and ceiling values
Code
C++
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
pair<int*, int*> floorCeiling(TreeNode* root, int target) {
int* floor = nullptr;
int* ceiling = nullptr;
TreeNode* curr = root;
while (curr) {
if (curr->val == target) {
floor = new int(curr->val);
ceiling = new int(curr->val);
break;
} else if (curr->val < target) {
floor = new int(curr->val);
curr = curr->right;
} else {
ceiling = new int(curr->val);
curr = curr->left;
}
}
return {floor, ceiling};
}
};
Go
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func floorCeiling(root *TreeNode, target int) (*int, *int) {
var floor, ceiling *int
curr := root
for curr != nil {
if curr.Val == target {
val := curr.Val
floor = &val
ceiling = &val
break
} else if curr.Val < target {
val := curr.Val
floor = &val
curr = curr.Right
} else {
val := curr.Val
ceiling = &val
curr = curr.Left
}
}
return floor, ceiling
}
Java
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) { this.val = val; }
}
class Solution {
public int[] floorCeiling(TreeNode root, int target) {
Integer floor = null;
Integer ceiling = null;
TreeNode curr = root;
while (curr != null) {
if (curr.val == target) {
floor = curr.val;
ceiling = curr.val;
break;
} else if (curr.val < target) {
floor = curr.val;
curr = curr.right;
} else {
ceiling = curr.val;
curr = curr.left;
}
}
return new int[] {
floor != null ? floor : -1,
ceiling != null ? ceiling : -1
};
}
}
Python
class TreeNode:
def __init__(self, val: int = 0, left: 'TreeNode' = None, right: 'TreeNode' = None):
self.val = val
self.left = left
self.right = right
class Solution:
def floorCeiling(self, root: TreeNode, target: int) -> tuple[int, int]:
floor = None
ceiling = None
curr = root
while curr:
if curr.val == target:
floor = curr.val
ceiling = curr.val
break
elif curr.val < target:
floor = curr.val
curr = curr.right
else:
ceiling = curr.val
curr = curr.left
return (floor, ceiling)
Complexity
- ⏰ Time complexity:
O(h), where h is the height of the BST (O(log n) for balanced, O(n) for skewed) - 🧺 Space complexity:
O(1), only using constant extra space for variables
Method 2 - Recursive BST Traversal
Intuition
We can solve this recursively by exploring both directions of the BST. For floor, we keep track of the best candidate less than or equal to target. For ceiling, we track the best candidate greater than or equal to target. The recursive approach naturally handles the BST property.
Approach
- Create separate recursive functions for finding floor and ceiling
- For floor:
- If current node is null, return None
- If node value equals target, return it
- If node value > target, search left subtree
- If node value < target, this could be floor, but check right subtree for better option
- For ceiling:
- If current node is null, return None
- If node value equals target, return it
- If node value < target, search right subtree
- If node value > target, this could be ceiling, but check left subtree for better option
- Combine both results and return
Code
C++
class Solution {
private:
int findFloor(TreeNode* root, int target) {
if (!root) return -1;
if (root->val == target) return root->val;
if (root->val > target) {
return findFloor(root->left, target);
}
int floorVal = findFloor(root->right, target);
return (floorVal != -1) ? floorVal : root->val;
}
int findCeiling(TreeNode* root, int target) {
if (!root) return -1;
if (root->val == target) return root->val;
if (root->val < target) {
return findCeiling(root->right, target);
}
int ceilVal = findCeiling(root->left, target);
return (ceilVal != -1) ? ceilVal : root->val;
}
public:
pair<int, int> floorCeiling(TreeNode* root, int target) {
int floor = findFloor(root, target);
int ceiling = findCeiling(root, target);
return {floor, ceiling};
}
};
Go
func findFloor(root *TreeNode, target int) *int {
if root == nil {
return nil
}
if root.Val == target {
val := root.Val
return &val
}
if root.Val > target {
return findFloor(root.Left, target)
}
if floorVal := findFloor(root.Right, target); floorVal != nil {
return floorVal
}
val := root.Val
return &val
}
func findCeiling(root *TreeNode, target int) *int {
if root == nil {
return nil
}
if root.Val == target {
val := root.Val
return &val
}
if root.Val < target {
return findCeiling(root.Right, target)
}
if ceilVal := findCeiling(root.Left, target); ceilVal != nil {
return ceilVal
}
val := root.Val
return &val
}
func floorCeilingRecursive(root *TreeNode, target int) (*int, *int) {
floor := findFloor(root, target)
ceiling := findCeiling(root, target)
return floor, ceiling
}
Java
class Solution {
private Integer findFloor(TreeNode root, int target) {
if (root == null) return null;
if (root.val == target) return root.val;
if (root.val > target) {
return findFloor(root.left, target);
}
Integer floorVal = findFloor(root.right, target);
return floorVal != null ? floorVal : root.val;
}
private Integer findCeiling(TreeNode root, int target) {
if (root == null) return null;
if (root.val == target) return root.val;
if (root.val < target) {
return findCeiling(root.right, target);
}
Integer ceilVal = findCeiling(root.left, target);
return ceilVal != null ? ceilVal : root.val;
}
public int[] floorCeilingRecursive(TreeNode root, int target) {
Integer floor = findFloor(root, target);
Integer ceiling = findCeiling(root, target);
return new int[] {
floor != null ? floor : -1,
ceiling != null ? ceiling : -1
};
}
}
Python
class Solution:
def _findFloor(self, root: TreeNode, target: int) -> int:
if not root:
return None
if root.val == target:
return root.val
if root.val > target:
return self._findFloor(root.left, target)
floor_val = self._findFloor(root.right, target)
return floor_val if floor_val is not None else root.val
def _findCeiling(self, root: TreeNode, target: int) -> int:
if not root:
return None
if root.val == target:
return root.val
if root.val < target:
return self._findCeiling(root.right, target)
ceil_val = self._findCeiling(root.left, target)
return ceil_val if ceil_val is not None else root.val
def floorCeilingRecursive(self, root: TreeNode, target: int) -> tuple[int, int]:
floor = self._findFloor(root, target)
ceiling = self._findCeiling(root, target)
return (floor, ceiling)
Complexity
- ⏰ Time complexity:
O(h), where h is the height of the BST, visiting each node at most once - 🧺 Space complexity:
O(h), recursion stack depth equals the height of the tree