Lower Bound and Upper Bound
Table of Contents + β
Say you have a sorted list of numbers. You want to find where a value fits. Not just βis it there or notβ like plain search. You want the first spot where things become greater-than-or-equal to your target. Or the first spot where things become strictly-greater. That is exactly what lower bound and upper bound give you. Let me show you.
π What Are Lower Bound and Upper Bound?
Here is the pain first. Plain binary search only tells you yes it exists or no it does not. But many problems need more than that. You want to know where a number would sit. You want to count how many numbers are below it. You want the range of all copies of a value. Plain search cannot answer that cleanly.
So we use two close cousins of binary search.
- Lower bound is the index of the first element that is greater-than-or-equal to the target.
- Upper bound is the index of the first element that is strictly-greater than the target.
Both work on a sorted array. A sorted array means the numbers go from small to large, left to right. Both run in O(log n) time because they jump in halves, just like binary search.
Note
If no element fits the condition, both bounds return n, the length of the array. That is one step past the last index. Think of it as βit belongs at the very end.β
π― A Small Example to See the Difference
Let us take this sorted array. The index sits above each value.
index: 0 1 2 3 4 5value: 10 20 20 20 40 50Now say the target is 20. Let us read both bounds slowly.
- Lower bound of
20looks for the first value that is greater-than-or-equal to20. That is index1. The value at index0is10, which is too small. Index1is the first20. So lower bound is1. - Upper bound of
20looks for the first value that is strictly-greater than20. The20s do not count. The first value bigger than20is40at index4. So upper bound is4.
See the neat trick here? Upper bound minus lower bound gives the count of how many times 20 appears. Here that is 4 - 1 = 3. And yes, there are three 20s. Very handy.
Let us look at a few more targets on the same array.
| Target | Lower Bound (first greater-or-equal) | Upper Bound (first strictly-greater) |
|---|---|---|
| 10 | 0 | 1 |
| 20 | 1 | 4 |
| 30 | 4 | 4 |
| 50 | 5 | 6 |
| 99 | 6 | 6 |
Notice the target 30. It is not even in the array, yet both bounds return 4 - the spot where 30 would go. And 99 is bigger than everything, so both return 6, which is n. That matches our note from before.
π οΈ How the Loop Works
Both bounds use the same binary-search shape. You keep a window with a low and a high pointer. You look at the middle. Based on a check, you drop one half. You repeat until the window closes. The only thing that changes between the two is the comparison.
Here is the idea in plain words.
- Start with
low = 0andhigh = n. Note thathighisn, notn - 1. We allow the answer to land at the very end. - Find the middle:
mid = (low + high) / 2. - For lower bound: if the value at
midis greater-than-or-equal to the target, the answer could bemidor to its left. So sethigh = mid. Otherwise move right withlow = mid + 1. - For upper bound: change only the check. If the value at
midis strictly-greater than the target, sethigh = mid. Otherwiselow = mid + 1. - When
lowequalshigh, that meeting point is your answer.
The single difference is whether equal values are pushed left or right. Lower bound keeps equal values on the right side of the cut. So it lands on the first equal one. Upper bound pushes equal values to the left side of the cut. So it lands past them.
Steps to find Lower Bound
- Set
low = 0andhigh = n(the array length). - While
lowis less thanhigh, computemid = low + (high - low) / 2. - If
arr[mid]is greater-than-or-equal to the target, sethigh = mid. - Otherwise set
low = mid + 1. - When the loop ends, return
low. That is the lower bound index.
Steps to find Upper Bound
- Set
low = 0andhigh = n(the array length). - While
lowis less thanhigh, computemid = low + (high - low) / 2. - If
arr[mid]is strictly-greater than the target, sethigh = mid. - Otherwise set
low = mid + 1. - When the loop ends, return
low. That is the upper bound index.
π» Code in Five Languages
Below, each program builds the same sorted array [10, 20, 20, 20, 40, 50] and finds both bounds for the target 20. Watch the output match what we worked out by hand: lower bound 1, upper bound 4.
# First index where arr[i] is greater-than-or-equal to targetdef lower_bound(arr, target): low, high = 0, len(arr) while low < high: mid = low + (high - low) // 2 if arr[mid] >= target: high = mid # answer is mid or to the left else: low = mid + 1 # answer is to the right return low
# First index where arr[i] is strictly-greater than targetdef upper_bound(arr, target): low, high = 0, len(arr) while low < high: mid = low + (high - low) // 2 if arr[mid] > target: high = mid else: low = mid + 1 return low
arr = [10, 20, 20, 20, 40, 50]target = 20print(f"Lower bound of {target} = {lower_bound(arr, target)}")print(f"Upper bound of {target} = {upper_bound(arr, target)}")The output of the above code will be:
Lower bound of 20 = 1Upper bound of 20 = 4Tip
The two functions are almost twins. The only change is the compare: lower bound uses greater-than-or-equal, upper bound uses strictly-greater. Remember that one line and you can write both from memory.
β±οΈ Time and Space Complexity
Both bounds cut the search window in half every step. So a million elements take only about twenty steps. That is the O(log n) win.
Note
Lower bound and upper bound each run in O(log n) time and O(1) extra space. They do the work in place with just a few index variables.
| Operation | Time | Space |
|---|---|---|
| Lower bound | O(log n) | O(1) |
| Upper bound | O(log n) | O(1) |
| Count of a value (upper minus lower) | O(log n) | O(1) |
β οΈ Common Mistakes
A few traps catch people every time. Watch out for these.
- Setting
high = n - 1instead ofhigh = n. Then the answer can never land at the end. You miss cases where the target is bigger than everything. - Mixing up the two checks. Lower bound is greater-than-or-equal. Upper bound is strictly-greater. Swap them by accident and your bounds flip.
- Running it on an array that is not sorted. Both bounds only work when the array goes small to large. On unsorted data the result is meaningless.
- Writing
mid = (low + high) / 2on very large arrays where the sum overflows. Uselow + (high - low) / 2to stay safe. - Forgetting that the returned value is an index, not the element. If you want the value, check the index is less than
nfirst, then readarr[index].
π§© What Youβve Learned
- β Lower bound is the index of the first element greater-than-or-equal to the target.
- β Upper bound is the index of the first element strictly-greater than the target.
- β Both run on a sorted array using a binary-search loop in O(log n) time.
- β The only difference between them is the comparison: greater-than-or-equal versus strictly-greater.
- β Upper bound minus lower bound gives the count of how many times a value appears.
- β
When no element fits, both return
n, the position one past the last index.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
On the sorted array [10, 20, 20, 20, 40, 50], what is the lower bound of 20?
Why: Lower bound is the first index whose value is greater-than-or-equal to 20, which is the first 20 at index 1.
- 2
What is the only real difference between the lower bound and upper bound loops?
Why: Everything else is identical; only the comparison changes between greater-than-or-equal and strictly-greater.
- 3
If a target is larger than every element in the array of length n, what do both bounds return?
Why: When nothing fits the condition, both bounds return n, the position one past the last index.
- 4
How do you count how many times a value appears using these bounds?
Why: Upper bound minus lower bound gives the number of elements equal to the target.