Finding first/last occurrence — variant boundary handling. — Cracked Java
// Data Structures & Algorithms · Searching & Binary Search
MidTheoryCoding

Finding first/last occurrence — variant boundary handling.

Problem. In a sorted array with duplicates, find the first and last index of a target. Plain binary search returns some match — how do you control which boundary you land on?

Why plain search isn't enough

In [5, 7, 7, 7, 8, 8, 10], searching for 7 might return index 1, 2, or 3 depending on where mid lands. To pin down the first or last occurrence, you must not return on the first hit — instead, treat a match as "a valid candidate, but keep narrowing toward the boundary."

The lower-bound / upper-bound trick

The clean formulation is two helpers. lowerBound returns the first index whose value is >= target; upperBound returns the first index whose value is > target. From those, the first occurrence is lowerBound(target) (if it actually equals target) and the last is upperBound(target) - 1.

// first index with a[i] >= target  (insertion point on the left)
int lowerBound(int[] a, int target) {
    int lo = 0, hi = a.length;          // exclusive hi -> [lo, hi)
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] < target) lo = mid + 1;   // mid too small -> go right
        else hi = mid;                       // a[mid] >= target -> mid is a candidate
    }
    return lo;
}

// first index with a[i] > target
int upperBound(int[] a, int target) {
    int lo = 0, hi = a.length;
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] <= target) lo = mid + 1;  // mid <= target -> boundary is further right
        else hi = mid;
    }
    return lo;
}

int[] searchRange(int[] a, int target) {
    int first = lowerBound(a, target);
    if (first == a.length || a[first] != target) return new int[]{-1, -1};
    int last = upperBound(a, target) - 1;
    return new int[]{first, last};
}

O(log n) per bound, O(1) space.

The boundary handling that matters

The whole technique is the else hi = mid branch: on a candidate you shrink the window without discarding mid, because a still-earlier occurrence may exist. The only difference between the two bounds is < vs <= in the comparison — that single character decides whether equal elements push the boundary left or right.

Mark your status