You are 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.
Example 1:
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]Example 2:
Input: nums = [1], target = 1
Output: [0,0]Constraints:
0 <= nums.length <= 100,000-1,000,000,000 <= nums[i], target <= 1,000,000,000nums is a non-decreasing array.Since the array is sorted, all occurrences of the target will be consecutive. We can scan through the array once, recording the first and last positions where we encounter the target. The first time we see the target, we set both the start and end to that index. For subsequent matches, we only update the end position.
res = [-1, -1].i:nums[i] equals the target:res[0] is still -1, set both res[0] and res[1] to i.res[1] to i.res.Binary search can find any occurrence of the target in O(log n) time, but we need both the first and last occurrences. The key insight is that when we find the target, instead of returning immediately, we continue searching. For the leftmost occurrence, we search the left half after finding a match. For the rightmost, we search the right half. This gives us two separate binary searches with a bias parameter.
leftBias parameter.nums[m] == target:m as a candidate.leftBias is true, continue searching left (r = m - 1).l = m + 1).leftBias = true for the start position, and once with leftBias = false for the end position.[left, right].class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left = self.binarySearch(nums, target, True)
right = self.binarySearch(nums, target, False)
return [left, right]
def binarySearch(self, nums, target, leftBias):
l, r = 0, len(nums) - 1
i = -1
while l <= r:
m = (l + r) // 2
if target > nums[m]:
l = m + 1
elif target < nums[m]:
r = m - 1
else:
i = m
if leftBias:
r = m - 1
else:
l = m + 1
return iWe can use a single style of binary search that finds the insertion point for a value. The insertion point for target gives us the first occurrence (if it exists). The insertion point for target + 1 gives us one past the last occurrence. This approach uses the standard lower-bound binary search pattern, which finds the smallest index where nums[index] >= target.
nums[m] >= target.start = binarySearch(target).start equals the array length or nums[start] != target, return [-1, -1].end = binarySearch(target + 1) - 1.[start, end].class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
def binarySearch(target):
l, r = 0, n
while l < r:
m = l + (r - l) // 2
if nums[m] >= target:
r = m
else:
l = m + 1
return l
start = binarySearch(target)
if start == n or nums[start] != target:
return [-1, -1]
return [start, binarySearch(target + 1) - 1]Most programming languages provide built-in functions for binary search that find lower and upper bounds. These functions are optimized and well-tested, making them a practical choice when available. The lower bound gives the first occurrence, and the upper bound (minus one) gives the last occurrence.
[-1, -1].[lower_bound, upper_bound - 1].Standard binary search returns immediately upon finding the target, but this gives an arbitrary occurrence, not the first or last. You must continue searching (leftward for first, rightward for last) even after finding a match to locate the boundary positions.
After performing binary search for the first occurrence, you must verify that the found index actually contains the target. If the target does not exist in the array, the binary search returns an insertion point, not a valid position. Always check bounds and value equality before returning.
When using lower-bound and upper-bound style searches, the upper bound points to one past the last occurrence. Forgetting to subtract 1 when computing the end position, or using incorrect loop conditions (l <= r vs l < r), leads to wrong indices or infinite loops.