Lower Bound, Upper Bound & Element Occurrences
C++ std::lower_bound and upper_bound behavior, first and last occurrence extraction, and range frequency calculations.
Topics Covered:
51. Binary Search Invariants & Interval Models • 52. Lower Bound Algorithm () • Upper Bound Algorithm () • 53. First and Last Occurrences & Frequency Counting in
Beyond locating exact single-element matches, binary search serves as a precision tool for locating boundaries in sorted sequences. Standard binary search halts unpredictably on any arbitrary instance of a duplicate target. In production algorithms, standard libraries (such as C++ std::lower_bound and std::upper_bound, Java Arrays.binarySearch, and Python bisect_left/bisect_right) rely on invariant-preserving predicates to find the exact boundaries of duplicate runs. This chapter formalizes binary search interval models, strict versus non-strict monotonic boundary predicates, candidate-tracking state machines, and frequency range evaluations.
Learning Objectives
#- Differentiate the three fundamental binary search interval paradigms (Closed , Half-Open , and Open ).
- Formulate the exact mathematical predicates defining Lower Bound () and Upper Bound ().
- Prove why the total frequency of any element in a sorted array equals in guaranteed time.
- Implement dedicated
FirstOccurrenceandLastOccurrencevariants using candidate-retention variables and directional interval compression. - Trace boundary conditions where the search key is strictly smaller than , strictly greater than , or present across contiguous duplicate spans.
Topic 51: Binary Search Interval Invariants
#1. The Three Interval Paradigms
#A significant portion of binary search bugs—including infinite loops, off-by-one errors, and array boundary violations—stem from mixing interval models. A production implementation must maintain strict consistency across its loop condition and pointer updates:
| Interval Model | Mathematical Window | Loop Invariant Condition | Left Pointer Update | Right Pointer Update | Post-Loop Termination State |
|---|---|---|---|---|---|
| Model 1: Closed Interval | while low <= high: | ||||
| Model 2: Half-Open Interval | while low < high: | ||||
| Model 3: Open Interval | while low + 1 < high: |
Standardization Note: Throughout this chapter, algorithms are formulated under Model 1 (Closed Interval with Candidate Retention), which guarantees safe convergence without boundary-pointer underflow.
Topic 52: Lower Bound & Upper Bound Mathematics
#1. Lower Bound: Mathematical Definition & Mechanics
#Given a sorted array of elements in non-decreasing order, the Lower Bound of target is the smallest index such that:
If all elements in are strictly smaller than target, the lower bound returns (representing the theoretical insertion index at the end of the array).
Lower Bound Query Matrix (, )
| Query Target | Evaluated Predicate () | First Qualifying Element | Resulting Index | Algorithmic Rationale |
|---|---|---|---|---|
8 | 8 | 3 | First duplicate instance of 8 | |
7 | 8 | 3 | Value 7 absent; 8 is the smallest element | |
2 | 2 | 0 | First element satisfies condition | |
15 | None | 8 () | All elements ; returns array length |
Canonical Lower Bound Implementation:
FUNCTION LowerBound(A: Array of Element, n: Integer, target: Element) -> Integer:
low <- 0
high <- n - 1
ans <- n // Default if all elements < target
while low <= high:
mid <- low + (high - low) / 2
if A[mid] >= target:
ans <- mid // Candidate found; search left for earlier occurrence
high <- mid - 1
else:
low <- mid + 1 // A[mid] too small; search right half
return ans2. Upper Bound: Mathematical Definition & Mechanics
#Given a sorted array of elements in non-decreasing order, the Upper Bound of target is the smallest index such that:
If no element in is strictly greater than target, the upper bound returns .
Upper Bound Query Matrix (, )
| Query Target | Evaluated Predicate () | First Qualifying Element | Resulting Index | Algorithmic Rationale |
|---|---|---|---|---|
8 | 10 | 6 | First element strictly greater than 8 | |
5 | 6 | 2 | Smallest element strictly greater than 5 | |
12 | None | 8 () | No elements ; returns array length |
Canonical Upper Bound Implementation:
FUNCTION UpperBound(A: Array of Element, n: Integer, target: Element) -> Integer:
low <- 0
high <- n - 1
ans <- n // Default if no element > target
while low <= high:
mid <- low + (high - low) / 2
if A[mid] > target:
ans <- mid // Candidate found; search left for smaller index
high <- mid - 1
else:
low <- mid + 1 // A[mid] <= target; search right half
return ansTopic 53: Element Occurrences & Frequency Counting
#1. The Range Extraction Theorem
#In an unsorted array, counting the occurrences of a value requires a full linear scan ( time). In a sorted array, duplicate elements form an unbroken contiguous subarray:
Existence Verification Rule:
The target exists in array if and only if:
2. Step-by-Step Range Trace: Target in
#| Sub-Algorithm | low | high | mid | Predicate Evaluation | Window Update | |
|---|---|---|---|---|---|---|
| Lower Bound Pass | 0 | 7 | 3 | 8 | (True) | |
0 | 2 | 1 | 4 | (False) | ||
2 | 2 | 2 | 6 | (False) | ||
| Terminates | LowerBound Result | ans = 3 | ||||
| Upper Bound Pass | 0 | 7 | 3 | 8 | (False) | |
4 | 7 | 5 | 8 | (False) | ||
6 | 7 | 6 | 10 | (True) | ||
| Terminates | UpperBound Result | ans = 6 |
Operational Output:
- First Occurrence: Index ()
- Last Occurrence: ()
- Total Frequency: instances of value 8!
- Total Time: Two binary searches .
3. Dedicated First and Last Occurrence Functions
#When only one boundary is required, dedicated functions avoid invoking two separate passes:
FUNCTION FirstOccurrence(A: Array of Element, n: Integer, target: Element) -> Integer:
low <- 0
high <- n - 1
ans <- -1
while low <= high:
mid <- low + (high - low) / 2
if A[mid] == target:
ans <- mid
high <- mid - 1 // Contract right boundary to search earlier indices
else if A[mid] < target:
low <- mid + 1
else:
high <- mid - 1
return ans
FUNCTION LastOccurrence(A: Array of Element, n: Integer, target: Element) -> Integer:
low <- 0
high <- n - 1
ans <- -1
while low <= high:
mid <- low + (high - low) / 2
if A[mid] == target:
ans <- mid
low <- mid + 1 // Contract left boundary to search later indices
else if A[mid] < target:
low <- mid + 1
else:
high <- mid - 1
return ans4. Key Takeaways
#- Predicate Distinction: Lower Bound uses a non-strict inequality (); Upper Bound uses a strict inequality ().
- Fallback Index: If no element satisfies the predicate, both Lower and Upper Bound return (the valid insertion position).
- Range Counting: The exact frequency of any element in a sorted array is computed in time as .
- Candidate Tracking: Initializing
ans = nand contracting the active window when a candidate is identified guarantees safe convergence without off-by-one errors.
Academic Attribution & References
#- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Section 2.3 & Chapter 12. MIT Press.
- Bentley, J. (2000). Programming Pearls (2nd ed.), Column 4: Writing Correct Programs. Addison-Wesley.
- Knuth, D. E. (1998). The Art of Computer Programming, Volume 3: Sorting and Searching (2nd ed.), Section 6.2: Searching by Comparison of Keys. Addison-Wesley.
- Stepanov, A., & Lee, M. (1995). The Standard Template Library (STL). HP Laboratories Technical Report.