BST queries
Ordered queries on a BST: floor and ceiling, lower and upper bounds, the k-th smallest key, rank, range sums and range traversals.
Learning goals
- Find the nearest keys at or around a value with floor, ceiling, lower and higher
- Answer k-th smallest and rank with size fields in O(h)
- Sum or list the keys of a range while skipping whole subtrees
Prerequisites
A hash table answers 'is this key present?'. An ordered structure also answers 'what is the closest key?', 'how many keys are smaller?' and 'which keys lie in this range?', all in O(h).
Floor/ceiling
The **floor** of x is the largest key that is at most x; the **ceiling** is the smallest key that is at least x. Walk down as in a search, and whenever the current key is a *candidate* remember it and look for a better one on the other side. For floor, a key that is too big sends the walk left; a key that fits is a candidate and sends it right.
In the 7-key tree, the “Floor of an absent key” preset gives 60 for 65 and the “Ceiling of an absent key” preset gives 70. For 10 there is no floor (the “No floor exists” preset): every key is bigger, so nothing ever becomes a candidate.
1static Integer floor(Node n, int x) { // largest key <= x2Integer ans = null;3while (n != null) {4if (n.key == x) return n.key;5if (n.key > x) n = n.left; // too big: look left6else { ans = n.key; n = n.right; } // a candidate: look right for a closer one7}8return ans;9}10static Integer ceiling(Node n, int x) { // smallest key >= x11Integer ans = null;12while (n != null) {13if (n.key == x) return n.key;14if (n.key < x) n = n.right;15else { ans = n.key; n = n.left; }16}17return ans;18}
Best
O(1)
Average
O(log n)
Worst
O(n)
Space
O(1)
BST queries: floor, ceiling, predecessor, successor, k-th smallest, rank, range sum
Interactive · edit the inputbinary search tree · 7 nodes
A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x).
1static class Node { int key, size = 1; Node left, right; Node(int k) { key = k; } }2static Integer floor(Node n, int x) { // largest key <= x3Integer ans = null;4while (n != null) {5if (n.key == x) return n.key;6if (n.key > x) n = n.left; // too big: look left7else { ans = n.key; n = n.right; } // a candidate: look right for a closer one8}9return ans;10}11static Integer ceiling(Node n, int x) { // smallest key >= x12Integer ans = null;13while (n != null) {14if (n.key == x) return n.key;15if (n.key < x) n = n.right;16else { ans = n.key; n = n.left; }17}18return ans;19}20static Integer lower(Node n, int x) { // largest key < x: the predecessor21Integer ans = null;22while (n != null) {23if (n.key >= x) n = n.left;24else { ans = n.key; n = n.right; }25}26return ans;27}28static Integer higher(Node n, int x) { // smallest key > x: the successor29Integer ans = null;30while (n != null) {31if (n.key <= x) n = n.right;32else { ans = n.key; n = n.left; }33}34return ans;35}36static int size(Node n) { return n == null ? 0 : n.size; }37static int kthSmallest(Node n, int k) { // 1-based; needs the size fields38while (n != null) {39int left = size(n.left);40if (k <= left) n = n.left;41else if (k == left + 1) return n.key;42else { k -= left + 1; n = n.right; }43}44throw new NoSuchElementException();45}46static int rank(Node n, int x) { // how many keys are < x47int r = 0;48while (n != null) {49if (x <= n.key) n = n.left;50else { r += size(n.left) + 1; n = n.right; }51}52return r;53}54static int rangeSum(Node n, int lo, int hi) {55if (n == null) return 0;56int sum = 0;57if (n.key > lo) sum += rangeSum(n.left, lo, hi); // the left subtree may hold keys >= lo58if (lo <= n.key && n.key <= hi) sum += n.key;59if (n.key < hi) sum += rangeSum(n.right, lo, hi); // the right subtree may hold keys <= hi60return sum;61}62static Integer minimum(Node n) { if (n == null) return null; while (n.left != null) n = n.left; return n.key; }63static Integer maximum(Node n) { if (n == null) return null; while (n.right != null) n = n.right; return n.key; }
Live state
keys
7
height
2
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | keys=7 height=2 | A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x). |
| 2 | L7 | x=65 | 50 <= 65: 50 is a candidate for the floor; a larger one can only be in its right subtree. |
| 3 | L6 | x=65 | 70 > 65: too big to be the floor, so discard it and its whole right subtree and go left. |
| 4 | L7 | x=65 | 60 <= 65: 60 is a candidate for the floor; a larger one can only be in its right subtree. |
| 5 | L4 | x=65 result=60 | The floor of 65 is 60. |
Lower/upper bound
Java's navigable collections name four neighbours: floor (at most), ceiling (at least), lower (strictly less) and higher (strictly greater). The strict ones are the predecessor and the successor of a value that may or may not be in the tree. Other languages call things differently: C++ lower_bound is Java's ceiling and upper_bound is higher. Always check which one a problem means.
The code differs from floor and ceiling only in using < and > instead of <= and >=. In the “Predecessor of a key” preset, the lower neighbour of 50 is 40; in the “Successor is an ancestor” preset the higher neighbour of 40 is 50.
1static Integer lower(Node n, int x) { // largest key < x2Integer ans = null;3while (n != null) {4if (n.key >= x) n = n.left;5else { ans = n.key; n = n.right; }6}7return ans;8}9static Integer higher(Node n, int x) { // smallest key > x10Integer ans = null;11while (n != null) {12if (n.key <= x) n = n.right;13else { ans = n.key; n = n.left; }14}15return ans;16}
Best
O(1)
Average
O(log n)
Worst
O(n)
Space
O(1)
BST queries: floor, ceiling, predecessor, successor, k-th smallest, rank, range sum
Interactive · edit the inputbinary search tree · 7 nodes
A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x).
1static class Node { int key, size = 1; Node left, right; Node(int k) { key = k; } }2static Integer floor(Node n, int x) { // largest key <= x3Integer ans = null;4while (n != null) {5if (n.key == x) return n.key;6if (n.key > x) n = n.left; // too big: look left7else { ans = n.key; n = n.right; } // a candidate: look right for a closer one8}9return ans;10}11static Integer ceiling(Node n, int x) { // smallest key >= x12Integer ans = null;13while (n != null) {14if (n.key == x) return n.key;15if (n.key < x) n = n.right;16else { ans = n.key; n = n.left; }17}18return ans;19}20static Integer lower(Node n, int x) { // largest key < x: the predecessor21Integer ans = null;22while (n != null) {23if (n.key >= x) n = n.left;24else { ans = n.key; n = n.right; }25}26return ans;27}28static Integer higher(Node n, int x) { // smallest key > x: the successor29Integer ans = null;30while (n != null) {31if (n.key <= x) n = n.right;32else { ans = n.key; n = n.left; }33}34return ans;35}36static int size(Node n) { return n == null ? 0 : n.size; }37static int kthSmallest(Node n, int k) { // 1-based; needs the size fields38while (n != null) {39int left = size(n.left);40if (k <= left) n = n.left;41else if (k == left + 1) return n.key;42else { k -= left + 1; n = n.right; }43}44throw new NoSuchElementException();45}46static int rank(Node n, int x) { // how many keys are < x47int r = 0;48while (n != null) {49if (x <= n.key) n = n.left;50else { r += size(n.left) + 1; n = n.right; }51}52return r;53}54static int rangeSum(Node n, int lo, int hi) {55if (n == null) return 0;56int sum = 0;57if (n.key > lo) sum += rangeSum(n.left, lo, hi); // the left subtree may hold keys >= lo58if (lo <= n.key && n.key <= hi) sum += n.key;59if (n.key < hi) sum += rangeSum(n.right, lo, hi); // the right subtree may hold keys <= hi60return sum;61}62static Integer minimum(Node n) { if (n == null) return null; while (n.left != null) n = n.left; return n.key; }63static Integer maximum(Node n) { if (n == null) return null; while (n.right != null) n = n.right; return n.key; }
Live state
keys
7
height
2
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | keys=7 height=2 | A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x). |
| 2 | L7 | x=65 | 50 <= 65: 50 is a candidate for the floor; a larger one can only be in its right subtree. |
| 3 | L6 | x=65 | 70 > 65: too big to be the floor, so discard it and its whole right subtree and go left. |
| 4 | L7 | x=65 | 60 <= 65: 60 is a candidate for the floor; a larger one can only be in its right subtree. |
| 5 | L4 | x=65 result=60 | The floor of 65 is 60. |
Kth smallest
To find the **k-th smallest** key, store in every node the size of its subtree. At a node, let left be the size of its left subtree. If k <= left, the answer is in the left subtree. If k == left + 1, this node is it. Otherwise skip the left subtree and this node and look for number k - left - 1 on the right. The walk is O(h). Without size fields, an inorder traversal that stops at the k-th key costs O(h + k).
In the “3rd smallest” preset the sizes shown below the nodes drive the walk: the root has a left subtree of size 3, so the 3rd smallest is in the left subtree. At node 30 (left size 1) the key 3 is past 1 + 1 = 2 keys, so skip 30's left subtree and 30 itself and look for number 1 on the right: that is 40.
1static int size(Node n) { return n == null ? 0 : n.size; }23static int kthSmallest(Node n, int k) { // 1-based; every node keeps its subtree size4while (n != null) {5int left = size(n.left);6if (k <= left) n = n.left;7else if (k == left + 1) return n.key;8else { k -= left + 1; n = n.right; }9}10throw new NoSuchElementException();11}
Best
O(1)
Average
O(log n)
Worst
O(n)
Space
O(1)
BST queries: floor, ceiling, predecessor, successor, k-th smallest, rank, range sum
Interactive · edit the inputbinary search tree · 7 nodes
A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x).
1static class Node { int key, size = 1; Node left, right; Node(int k) { key = k; } }2static Integer floor(Node n, int x) { // largest key <= x3Integer ans = null;4while (n != null) {5if (n.key == x) return n.key;6if (n.key > x) n = n.left; // too big: look left7else { ans = n.key; n = n.right; } // a candidate: look right for a closer one8}9return ans;10}11static Integer ceiling(Node n, int x) { // smallest key >= x12Integer ans = null;13while (n != null) {14if (n.key == x) return n.key;15if (n.key < x) n = n.right;16else { ans = n.key; n = n.left; }17}18return ans;19}20static Integer lower(Node n, int x) { // largest key < x: the predecessor21Integer ans = null;22while (n != null) {23if (n.key >= x) n = n.left;24else { ans = n.key; n = n.right; }25}26return ans;27}28static Integer higher(Node n, int x) { // smallest key > x: the successor29Integer ans = null;30while (n != null) {31if (n.key <= x) n = n.right;32else { ans = n.key; n = n.left; }33}34return ans;35}36static int size(Node n) { return n == null ? 0 : n.size; }37static int kthSmallest(Node n, int k) { // 1-based; needs the size fields38while (n != null) {39int left = size(n.left);40if (k <= left) n = n.left;41else if (k == left + 1) return n.key;42else { k -= left + 1; n = n.right; }43}44throw new NoSuchElementException();45}46static int rank(Node n, int x) { // how many keys are < x47int r = 0;48while (n != null) {49if (x <= n.key) n = n.left;50else { r += size(n.left) + 1; n = n.right; }51}52return r;53}54static int rangeSum(Node n, int lo, int hi) {55if (n == null) return 0;56int sum = 0;57if (n.key > lo) sum += rangeSum(n.left, lo, hi); // the left subtree may hold keys >= lo58if (lo <= n.key && n.key <= hi) sum += n.key;59if (n.key < hi) sum += rangeSum(n.right, lo, hi); // the right subtree may hold keys <= hi60return sum;61}62static Integer minimum(Node n) { if (n == null) return null; while (n.left != null) n = n.left; return n.key; }63static Integer maximum(Node n) { if (n == null) return null; while (n.right != null) n = n.right; return n.key; }
Live state
keys
7
height
2
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | keys=7 height=2 | A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x). |
| 2 | L7 | x=65 | 50 <= 65: 50 is a candidate for the floor; a larger one can only be in its right subtree. |
| 3 | L6 | x=65 | 70 > 65: too big to be the floor, so discard it and its whole right subtree and go left. |
| 4 | L7 | x=65 | 60 <= 65: 60 is a candidate for the floor; a larger one can only be in its right subtree. |
| 5 | L4 | x=65 result=60 | The floor of 65 is 60. |
Rank
The **rank** of x is the number of keys smaller than x, the inverse of k-th smallest. Walk down as for a search; whenever the walk goes right, everything in the left subtree and the node itself is smaller than x, so add size(left) + 1. Going left adds nothing.
In the “Rank of 65” preset the rank is 5: the keys 20, 30, 40, 50 and 60 are smaller. The sizes shown under the nodes are what make each step O(1). Rank and k-th smallest together make an **order-statistic tree**.
1static int rank(Node n, int x) { // how many keys are < x2int r = 0;3while (n != null) {4if (x <= n.key) n = n.left;5else { r += size(n.left) + 1; n = n.right; }6}7return r;8}9static int size(Node n) { return n == null ? 0 : n.size; }
Best
O(1)
Average
O(log n)
Worst
O(n)
Space
O(1)
BST queries: floor, ceiling, predecessor, successor, k-th smallest, rank, range sum
Interactive · edit the inputbinary search tree · 7 nodes
A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x).
1static class Node { int key, size = 1; Node left, right; Node(int k) { key = k; } }2static Integer floor(Node n, int x) { // largest key <= x3Integer ans = null;4while (n != null) {5if (n.key == x) return n.key;6if (n.key > x) n = n.left; // too big: look left7else { ans = n.key; n = n.right; } // a candidate: look right for a closer one8}9return ans;10}11static Integer ceiling(Node n, int x) { // smallest key >= x12Integer ans = null;13while (n != null) {14if (n.key == x) return n.key;15if (n.key < x) n = n.right;16else { ans = n.key; n = n.left; }17}18return ans;19}20static Integer lower(Node n, int x) { // largest key < x: the predecessor21Integer ans = null;22while (n != null) {23if (n.key >= x) n = n.left;24else { ans = n.key; n = n.right; }25}26return ans;27}28static Integer higher(Node n, int x) { // smallest key > x: the successor29Integer ans = null;30while (n != null) {31if (n.key <= x) n = n.right;32else { ans = n.key; n = n.left; }33}34return ans;35}36static int size(Node n) { return n == null ? 0 : n.size; }37static int kthSmallest(Node n, int k) { // 1-based; needs the size fields38while (n != null) {39int left = size(n.left);40if (k <= left) n = n.left;41else if (k == left + 1) return n.key;42else { k -= left + 1; n = n.right; }43}44throw new NoSuchElementException();45}46static int rank(Node n, int x) { // how many keys are < x47int r = 0;48while (n != null) {49if (x <= n.key) n = n.left;50else { r += size(n.left) + 1; n = n.right; }51}52return r;53}54static int rangeSum(Node n, int lo, int hi) {55if (n == null) return 0;56int sum = 0;57if (n.key > lo) sum += rangeSum(n.left, lo, hi); // the left subtree may hold keys >= lo58if (lo <= n.key && n.key <= hi) sum += n.key;59if (n.key < hi) sum += rangeSum(n.right, lo, hi); // the right subtree may hold keys <= hi60return sum;61}62static Integer minimum(Node n) { if (n == null) return null; while (n.left != null) n = n.left; return n.key; }63static Integer maximum(Node n) { if (n == null) return null; while (n.right != null) n = n.right; return n.key; }
Live state
keys
7
height
2
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | keys=7 height=2 | A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x). |
| 2 | L7 | x=65 | 50 <= 65: 50 is a candidate for the floor; a larger one can only be in its right subtree. |
| 3 | L6 | x=65 | 70 > 65: too big to be the floor, so discard it and its whole right subtree and go left. |
| 4 | L7 | x=65 | 60 <= 65: 60 is a candidate for the floor; a larger one can only be in its right subtree. |
| 5 | L4 | x=65 result=60 | The floor of 65 is 60. |
Range sum
The sum of the keys in [lo, hi] is an inorder traversal that **prunes**: skip the left subtree when the current key is at most lo (everything there is smaller than lo) and skip the right subtree when the current key is at least hi. Only the nodes on the two boundary paths and the nodes inside the range are visited, so the cost is O(h + k) for k keys in the range, instead of O(n).
On the 7-key tree the “Range sum [35, 65]” preset sums 40 + 50 + 60 = 150 and visits 5 of the 7 nodes; the animation shades the skipped subtrees.
1static int rangeSum(Node n, int lo, int hi) {2if (n == null) return 0;3int sum = 0;4if (n.key > lo) sum += rangeSum(n.left, lo, hi); // the left subtree may hold keys >= lo5if (lo <= n.key && n.key <= hi) sum += n.key;6if (n.key < hi) sum += rangeSum(n.right, lo, hi); // the right subtree may hold keys <= hi7return sum;8}
Best
O(h)
Average
O(log n + k)
Worst
O(n)
Space
O(h)
BST queries: floor, ceiling, predecessor, successor, k-th smallest, rank, range sum
Interactive · edit the inputbinary search tree · 7 nodes
A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x).
1static class Node { int key, size = 1; Node left, right; Node(int k) { key = k; } }2static Integer floor(Node n, int x) { // largest key <= x3Integer ans = null;4while (n != null) {5if (n.key == x) return n.key;6if (n.key > x) n = n.left; // too big: look left7else { ans = n.key; n = n.right; } // a candidate: look right for a closer one8}9return ans;10}11static Integer ceiling(Node n, int x) { // smallest key >= x12Integer ans = null;13while (n != null) {14if (n.key == x) return n.key;15if (n.key < x) n = n.right;16else { ans = n.key; n = n.left; }17}18return ans;19}20static Integer lower(Node n, int x) { // largest key < x: the predecessor21Integer ans = null;22while (n != null) {23if (n.key >= x) n = n.left;24else { ans = n.key; n = n.right; }25}26return ans;27}28static Integer higher(Node n, int x) { // smallest key > x: the successor29Integer ans = null;30while (n != null) {31if (n.key <= x) n = n.right;32else { ans = n.key; n = n.left; }33}34return ans;35}36static int size(Node n) { return n == null ? 0 : n.size; }37static int kthSmallest(Node n, int k) { // 1-based; needs the size fields38while (n != null) {39int left = size(n.left);40if (k <= left) n = n.left;41else if (k == left + 1) return n.key;42else { k -= left + 1; n = n.right; }43}44throw new NoSuchElementException();45}46static int rank(Node n, int x) { // how many keys are < x47int r = 0;48while (n != null) {49if (x <= n.key) n = n.left;50else { r += size(n.left) + 1; n = n.right; }51}52return r;53}54static int rangeSum(Node n, int lo, int hi) {55if (n == null) return 0;56int sum = 0;57if (n.key > lo) sum += rangeSum(n.left, lo, hi); // the left subtree may hold keys >= lo58if (lo <= n.key && n.key <= hi) sum += n.key;59if (n.key < hi) sum += rangeSum(n.right, lo, hi); // the right subtree may hold keys <= hi60return sum;61}62static Integer minimum(Node n) { if (n == null) return null; while (n.left != null) n = n.left; return n.key; }63static Integer maximum(Node n) { if (n == null) return null; while (n.right != null) n = n.right; return n.key; }
Live state
keys
7
height
2
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | keys=7 height=2 | A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x). |
| 2 | L7 | x=65 | 50 <= 65: 50 is a candidate for the floor; a larger one can only be in its right subtree. |
| 3 | L6 | x=65 | 70 > 65: too big to be the floor, so discard it and its whole right subtree and go left. |
| 4 | L7 | x=65 | 60 <= 65: 60 is a candidate for the floor; a larger one can only be in its right subtree. |
| 5 | L4 | x=65 result=60 | The floor of 65 is 60. |
Range traversal
Collecting the keys in [lo, hi] uses the same pruned traversal in inorder, so the output is already sorted. This is what subMap and subSet views do in TreeMap and TreeSet. The total cost is O(h + k): descend to the boundary once, then walk the k results.
The difference from sorting the whole data: with a BST the first result is found in O(h) and the traversal can stop early, which fits 'list the next 10 keys after x' questions.
1static void range(Node n, int lo, int hi, List<Integer> out) {2if (n == null) return;3if (n.key > lo) range(n.left, lo, hi, out);4if (lo <= n.key && n.key <= hi) out.add(n.key); // inorder position: results come out sorted5if (n.key < hi) range(n.right, lo, hi, out);6}
Best
O(h)
Average
O(log n + k)
Worst
O(n)
Space
O(h)
BST queries: floor, ceiling, predecessor, successor, k-th smallest, rank, range sum
Interactive · edit the inputbinary search tree · 7 nodes
A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x).
1static class Node { int key, size = 1; Node left, right; Node(int k) { key = k; } }2static Integer floor(Node n, int x) { // largest key <= x3Integer ans = null;4while (n != null) {5if (n.key == x) return n.key;6if (n.key > x) n = n.left; // too big: look left7else { ans = n.key; n = n.right; } // a candidate: look right for a closer one8}9return ans;10}11static Integer ceiling(Node n, int x) { // smallest key >= x12Integer ans = null;13while (n != null) {14if (n.key == x) return n.key;15if (n.key < x) n = n.right;16else { ans = n.key; n = n.left; }17}18return ans;19}20static Integer lower(Node n, int x) { // largest key < x: the predecessor21Integer ans = null;22while (n != null) {23if (n.key >= x) n = n.left;24else { ans = n.key; n = n.right; }25}26return ans;27}28static Integer higher(Node n, int x) { // smallest key > x: the successor29Integer ans = null;30while (n != null) {31if (n.key <= x) n = n.right;32else { ans = n.key; n = n.left; }33}34return ans;35}36static int size(Node n) { return n == null ? 0 : n.size; }37static int kthSmallest(Node n, int k) { // 1-based; needs the size fields38while (n != null) {39int left = size(n.left);40if (k <= left) n = n.left;41else if (k == left + 1) return n.key;42else { k -= left + 1; n = n.right; }43}44throw new NoSuchElementException();45}46static int rank(Node n, int x) { // how many keys are < x47int r = 0;48while (n != null) {49if (x <= n.key) n = n.left;50else { r += size(n.left) + 1; n = n.right; }51}52return r;53}54static int rangeSum(Node n, int lo, int hi) {55if (n == null) return 0;56int sum = 0;57if (n.key > lo) sum += rangeSum(n.left, lo, hi); // the left subtree may hold keys >= lo58if (lo <= n.key && n.key <= hi) sum += n.key;59if (n.key < hi) sum += rangeSum(n.right, lo, hi); // the right subtree may hold keys <= hi60return sum;61}62static Integer minimum(Node n) { if (n == null) return null; while (n.left != null) n = n.left; return n.key; }63static Integer maximum(Node n) { if (n == null) return null; while (n.right != null) n = n.right; return n.key; }
Live state
keys
7
height
2
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | keys=7 height=2 | A BST of 7 keys, inorder [20, 30, 40, 50, 60, 70, 80]. Floor (largest key <= x). |
| 2 | L7 | x=65 | 50 <= 65: 50 is a candidate for the floor; a larger one can only be in its right subtree. |
| 3 | L6 | x=65 | 70 > 65: too big to be the floor, so discard it and its whole right subtree and go left. |
| 4 | L7 | x=65 | 60 <= 65: 60 is a candidate for the floor; a larger one can only be in its right subtree. |
| 5 | L4 | x=65 result=60 | The floor of 65 is 60. |
Practice ladder
Now try it yourself
Easy
- Range Sum of BST
- Closest Binary Search Tree Value
Medium
- Kth Smallest Element in a BST
- Count of Smaller Numbers After Self
Advanced
- Count of Range Sum
- Count of Smaller Numbers After Self (with a BST)
Key takeaways
- Floor, ceiling, lower and higher walk down once and remember the last candidate.
- C++ lower_bound is Java's ceiling and upper_bound is higher: check the naming.
- Subtree sizes give k-th smallest and rank in O(h).
- Range queries prune whole subtrees outside [lo, hi] and cost O(h + k).