More involved than I expected for a phone screen.
Start by clarifying the definitions of lower_bound and upper_bound and the expected behavior for edge cases. Then present a unified binary search template with explicit loop invariants, and walk through correctness arguments and complexity for each function.
Pro tip: Emphasize that lower_bound and upper_bound are the building blocks for counting occurrences and range queries, which are common in ML feature engineering and data preprocessing at scale. Mention that using a single template reduces off-by-one errors and makes the code easier to verify.
Define lower_bound as the first index where arr[i] >= target, and upper_bound as the first index where arr[i] > target. Explicitly state handling for empty arrays, duplicates, and targets outside the array range.
Use a half-open interval [lo, hi) with while lo < hi and mid = lo + (hi - lo) // 2. For lower_bound, if arr[mid] < target, lo = mid + 1; else hi = mid. For upper_bound, if arr[mid] <= target, lo = mid + 1; else hi = mid.
Invariant: answer lies in [lo, hi). Show that each iteration preserves the invariant and that termination yields lo == hi, which is the correct insertion point. Argue that the returned index satisfies the definition and that all elements before it are < target (for lower_bound) or <= target (for upper_bound).
Time complexity is O(log n) and space O(1). Walk through edge cases: empty array returns 0; target smaller than all elements returns 0; target larger than all elements returns n; duplicates are handled correctly because the search continues to the leftmost or rightmost boundary.
Mention how these functions enable counting occurrences (upper_bound - lower_bound) and range queries, which are useful in ML for bucketing continuous features, handling imbalanced datasets, and efficient lookups in sorted arrays.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.