Binary Search Trees · 16 of 22
Count BST nodes that lie in a given range
Count the numbers in a sorted tree that fall between a low and a high mark, skipping whole branches that are sure to be too small or too big.
- Simple way
- time O(n)
room O(h) - Clever way
- time O(h + k)
room O(h)
The problem, simply
You get a binary search tree (BST) and two numbers, low and high. Count how many numbers in the tree are between them, including low and high themselves.
A story to remember it
Picture it
Range 5 to 45. Five numbers fit: 5, 6, 10, 40, 45. The branches under 1 on the left and under 50 on the right can be skipped without looking.
- answer
- ignored
Examples
A middle range
- In
tree above, low = 5, high = 45- Out
5
5, 6, 10, 40 and 45. Both ends count.
Nothing fits
- In
tree above, low = 11, high = 39- Out
0
10 is too small and 40 is too big, and nothing sits between them.
Everything fits
- In
tree above, low = 0, high = 100- Out
10
Every number is inside, so we must visit all of them. No way can be faster than counting each one.
Low equals high
- In
tree above, low = 40, high = 40- Out
1
Counting how many numbers equal 40.
Empty tree
- In
[], low = 1, high = 9- Out
0
Nothing to count.
Way 1: the simple way
Visit every spot and check it.
- If the spot is
null, return 0. - Count 1 if
low <= val <= high, else 0. - Add the counts from both sides.
class Solution {
public int countInRange(TreeNode root, int low, int high) {
if (root == null) return 0;
int here = (root.val >= low && root.val <= high) ? 1 : 0;
return here + countInRange(root.left, low, high)
+ countInRange(root.right, low, high);
}
}How long it takes: every spot, O(n). Extra room: the recursion, O(h). Correct on any tree, but it ignores the order.
Way 2: the clever way
Use the order to skip whole sides.
- If the spot is
null, return 0. - If
val < low, this spot and its whole left side are too small. Only look at the right side. - If
val > high, this spot and its whole right side are too big. Only look at the left side. - Otherwise count this spot and look at both sides.
Counting 5 to 45 and skipping branches
01 / 07
10 is between 5 and 45. Count 1. Both sides might have more.
- low
- 5
- high
- 45
- count
- 1
- answer
- rejected
- ignored
class Solution {
public int countInRange(TreeNode root, int low, int high) {
if (root == null) return 0;
if (root.val < low) return countInRange(root.right, low, high); // left side is too small
if (root.val > high) return countInRange(root.left, low, high); // right side is too big
return 1 + countInRange(root.left, low, high) // I fit
+ countInRange(root.right, low, high);
}
}How long it takes: we visit the k spots that fit, plus at most two roads of spots that do not (one along the low edge, one along the high edge). So O(h + k). Extra room: the recursion, O(h).
Watch out for
- Both ends count: use
>=and<=. - A spot that is too small is not counted, but its right side might still hold numbers that fit. Do not stop there.
- For the sum version (LeetCode 938, range sum of BST), add
root.valinstead of 1, and uselongif the sum can be large.
What interviewers ask next
- Return the numbers, not the count, in sorted order. Same pruning, but walk left side, me, right side and add to a list.
- Many range questions on a fixed tree. Store in each spot the size of its part of the tree. Then "how many are
<= high" is one road down,O(h), and the answer iscountAtMost(high) - countAtMost(low - 1). - Remove every spot outside the range (LeetCode 669, trim a BST). Too small: return the trimmed right side. Too big: return the trimmed left side. Otherwise trim both children.
- Count how many full subtrees lie entirely inside the range. Each spot returns whether its whole part fits. A part fits if both children's parts fit and the spot fits.
Remember this
Grown-up words: skipping whole sides is pruning, the low and high marks are the bounds of a range query, k is the size of the answer, and storing part sizes in each spot makes an order statistic tree.