Two binary searches — one for the left boundary, one for the right. The key insight: run the same template twice with different conditions.
Given an array of integers nums sorted in non-decreasing order, find the starting and ending position of a given target value. If target is not found in the array, return [-1, -1]. You must write an algorithm with O(log n) runtime complexity.
This problem is really asking two separate questions:
nums[i] >= target?nums[i] <= target?If the element at the left boundary equals the target, the range [left, right] is the answer. If it doesn't equal the target, the target is not present and we return [-1, -1].
Detecting "target not present": After finding the left boundary, check nums[leftBoundary] == target. If this is false, the target doesn't exist and both boundaries return -1. This single check handles all "not found" cases.
Array: [5, 7, 7, 8, 8, 10], target = 8
7 >= 8? No → FALSE. Search right: left = 3.
8 >= 8? Yes → TRUE. Save boundary=4, search left: right = 3.
8 >= 8? Yes → TRUE. Save boundary=3, search left: right = 2.
Left boundary = 3. nums[3] = 8 = target → target exists.
class Solution { public int[] searchRange(int[] nums, int target) { int left = findFirst(nums, target); if (left == -1) return new int[]{-1, -1}; return new int[]{left, findLast(nums, target)}; } // First True: find first index where nums[i] >= target private int findFirst(int[] nums, int target) { int left = 0, right = nums.length - 1, boundary = -1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] >= target) { boundary = mid; right = mid - 1; } else { left = mid + 1; } } // Confirm target actually exists at the boundary if (boundary == -1 || nums[boundary] != target) return -1; return boundary; } // Last True: find last index where nums[i] <= target private int findLast(int[] nums, int target) { int left = 0, right = nums.length - 1, boundary = -1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] <= target) { boundary = mid; left = mid + 1; // search right for a later match } else { right = mid - 1; } } return boundary; } }
class Solution: def searchRange(self, nums: list[int], target: int) -> list[int]: left = self.find_first(nums, target) if left == -1: return [-1, -1] return [left, self.find_last(nums, target)] def find_first(self, nums, target): # First True: find first index where nums[i] >= target left, right, boundary = 0, len(nums) - 1, -1 while left <= right: mid = left + (right - left) // 2 if nums[mid] >= target: boundary = mid right = mid - 1 else: left = mid + 1 # Confirm target actually exists at the boundary if boundary == -1 or nums[boundary] != target: return -1 return boundary def find_last(self, nums, target): # Last True: find last index where nums[i] <= target left, right, boundary = 0, len(nums) - 1, -1 while left <= right: mid = left + (right - left) // 2 if nums[mid] <= target: boundary = mid left = mid + 1 # search right for a later match else: right = mid - 1 return boundary
Library connection: find_first is equivalent to bisect_left in Python and lower_bound in C++. find_last is equivalent to bisect_right - 1 and upper_bound - 1. This problem is exactly what those standard library functions implement.