Binary Search Techniques: Core Templates and Advanced Application Patterns
The foundation of efficient search operations relies on the divide-and-conquer principle applied to monotonic or structured datasets. The standard implementation maintains two pointers, typically left and right, converging toward a target state. To prevant infinite loops and ansure precise boundary detection, the condition left + 1 < right is widely adopted. Inside the loop, the midpoint is calculated using integer arithmetic to avoid overflow: mid = left + (right - left) / 2. Comparisons drive the pointer movement, and final verification occurs at the remaining endpoints.
Fundamental Implementation
public int search(int[] data, int target) {
if (data == null || data.length == 0) return -1;
int left = 0;
int right = data.length - 1;
while (left + 1 < right) {
int pivot = left + (right - left) / 2;
if (data[pivot] == target) {
return pivot;
} else if (data[pivot] < target) {
left = pivot;
} else {
right = pivot;
}
}
if (data[left] == target) return left;
if (data[right] == target) return right;
return -1;
}
This template guarantees termination becuase the search space shrinks by at least one element per iteration. Complexity remains logarithmic, O(log n).
Locating Boundaries in Monotonic Sequences
When the goal shifts from locating any instance to identifying the exact first or last index, the assignment logic within the comparison block changes. Maintaining invariant properties ensures boundaries are correctly narrowed.
First Occurrence
public int findFirst(int[] arr, int val) {
if (arr == null || arr.length == 0) return -1;
int low = 0;
int high = arr.length - 1;
while (low + 1 < high) {
int mid = low + (high - low) / 2;
if (arr[mid] >= val) {
high = mid; // Constrain right side to find earliest match
} else {
low = mid;
}
}
return arr[low] == val ? low : (arr[high] == val ? high : -1);
}
Last Occurrence
public int findLast(int[] arr, int val) {
if (arr == null || arr.length == 0) return -1;
int low = 0;
int high = arr.length - 1;
while (low + 1 < high) {
int mid = low + (high - low) / 2;
if (arr[mid] <= val) {
low = mid; // Expand left side to capture latest match
} else {
high = mid;
}
}
return arr[high] == val ? high : (arr[low] == val ? low : -1);
}
Determining the range [first, last] involves invoking these specialized searches sequentially. If either fails, the range does not exist.
Pattern-Based Searches and Domain Adaptations
Binary search adapts to various structural constraints. When dealing with version control systems or conditional checkpoints, the problem often maps to finding the first element satisfying a predicate (the OOXX pattern).
Version Control Boundary
public int firstFaultyVersion(int totalVersions) {
int lowerBound = 1;
int upperBound = totalVersions;
while (lowerBound + 1 < upperBound) {
int candidate = lowerBound + (upperBound - lowerBound) / 2;
if (isCompromised(candidate)) {
upperBound = candidate;
} else {
lowerBound = candidate;
}
}
return isCompromised(lowerBound) ? lowerBound : upperBound;
}
For unbounded or dynamically sized arrays, exponential probing establishes an upper bound before applying logarithmic reduction. This approach locates the search interval efficiently.
Rotated Sorted Sequences Rotated datasets break standard monotonicity but retain partial ordering. By comparing the midpoint with the boundaries, we can determine which partition contains the pivot point and recursively narrow the focus.
public int locateTargetRotated(int[] sequence, int key) {
if (sequence == null || sequence.length == 0) return -1;
int left = 0;
int right = sequence.length - 1;
while (left + 1 < right) {
int center = left + (right - left) / 2;
if (sequence[center] == key) return center;
if (sequence[left] <= sequence[center]) {
if (sequence[left] <= key && key < sequence[center]) {
right = center;
} else {
left = center;
}
} else {
if (sequence[center] < key && key <= sequence[right]) {
left = center;
} else {
right = center;
}
}
}
if (sequence[left] == key) return left;
if (sequence[right] == key) return right;
return -1;
}
Two-Dimensional Mappings Row-wise and column-wise sorted matrices can be linearized into a single conceptual array. Index conversion via modulo and division allows direct application of standard bounds.
public boolean existsInMatrix(int[][] grid, int query) {
if (grid == null || grid.length == 0 || grid[0].length == 0) return false;
int rows = grid.length;
int cols = grid[0].length;
int low = 0;
int high = rows * cols - 1;
while (low + 1 < high) {
int idx = low + (high - low) / 2;
int val = grid[idx / cols][idx % cols];
if (val == query) return true;
else if (val < query) low = idx;
else high = idx;
}
return grid[low / cols][low % cols] == query ||
grid[high / cols][high % cols] == query;
}
Result Space Optimization
Certain problems lack an explicit searchable array but possess a monotonic answer space. Binary search operates directly on potential outcomes rather than input indices.
Integer Square Root
public int computeSqrt(long number) {
if (number < 0) throw new IllegalArgumentException("Negative input");
if (number <= 1) return (int) number;
long lower = 1;
long upper = number;
while (lower + 1 < upper) {
long guess = lower + (upper - lower) / 2;
if (guess * guess <= number) {
lower = guess;
} else {
upper = guess;
}
}
return (int) lower;
}
Similar logic applies to allocation and partitioning problems, such as maximizing piece length given a cut constraint, or minimizing the maximum workload across processors. The feasibility function dictates directionality: if the current parameter yields sufficient output, the search space shifts upward; otherwise, it contracts downward.
Adjacent Algorithmic Variants
Extensions of binary search handle insertion points, proximity queries, and spatial bounding boxes efficiently.
Proximity Detection Locating the nearest value involves narrowing down to the immediate surrounding elements and performing constant-time comparisons.
public int nearestValue(int[] ordered, int reference) {
if (ordered == null || ordered.length == 0) return -1;
int ptr = 0;
int l = 0;
int r = ordered.length - 1;
while (l + 1 < r) {
int m = l + (r - l) / 2;
if (ordered[m] < reference) {
l = m;
} else {
r = m;
}
}
if (reference <= ordered[l]) return l;
if (reference >= ordered[r]) return r;
return (Math.abs(ordered[l] - reference) <= Math.abs(reference - ordered[r])) ? l : r;
}
Spatial Constraints Determining the minimal rectangular boundary enclosing specific pixels in a binary grid requires four independent boundary searches along axes. Each axis performs a directional scan to find the outermost active coordinates, combining them into the final area calculation. All presented methodologies prioritize deterministic convergence and strict boundary management. Adapting the core framework to specific problem constraints consistently yields optimal logarithmic performance without resortinging to brute-force enumeration.