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 = [5,7,7,8,8,10], target = 6 Output: [-1,-1]
Example 3:
Input: nums = [], target = 0 Output: [-1,-1]
Constraints:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums
is a non-decreasing array.-109 <= target <= 109
This problem requires finding the start and end indices of a target value within a sorted array. A naive approach would iterate through the array, but this is not optimal. The efficient solution leverages binary search for a logarithmic time complexity.
Approach:
The core idea is to perform two binary searches: one to find the leftmost occurrence of the target and another to find the rightmost occurrence.
Finding the Left Boundary: We modify the standard binary search to find the leftmost index where the target value is present. If the middle element is less than the target, we search in the right half; otherwise, we search in the left half, aiming to find the smallest index with the target value.
Finding the Right Boundary: Similarly, we perform another binary search to find the rightmost index. The key difference is that if the middle element is less than or equal to the target, we search in the right half; this ensures we find the largest index.
Handling Edge Cases: If the target is not found in either search, both left
and right
pointers will converge at the same index, indicating the target's absence. In this case, we return [-1, -1]
.
Time Complexity: O(log n) - Since we perform two binary searches, the overall time complexity remains logarithmic with respect to the input array's size.
Space Complexity: O(1) - The solution uses only a constant amount of extra space, regardless of the input array size.
Code Explanation (Python):
import bisect
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
l = bisect_left(nums, target) # Find leftmost occurrence
r = bisect_left(nums, target + 1) # Find leftmost occurrence of the next larger number
return [-1, -1] if l == r else [l, r - 1] # Return [-1,-1] if not found, otherwise [left, right]
The bisect_left
function from Python's bisect
module efficiently finds the insertion point for maintaining sorted order. bisect_left(nums, target)
returns the index where target
should be inserted to keep the array sorted. If target
exists, it points to the leftmost occurrence. bisect_left(nums, target + 1)
gives the index where the next larger element should be inserted. The difference between these two indices gives the range of the target.
Code Explanation (Java):
class Solution {
public int[] searchRange(int[] nums, int target) {
int l = search(nums, target);
int r = search(nums, target + 1);
return l == r ? new int[] {-1, -1} : new int[] {l, r - 1};
}
private int search(int[] nums, int x) {
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) >>> 1; // Right bit shift for faster division by 2
if (nums[mid] >= x) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
The Java code implements a similar strategy. The search
helper function performs a modified binary search, returning the index as described above.
The other language solutions follow the same algorithmic approach; only the syntax and specific binary search library functions may differ. The core logic remains consistent for optimal performance in finding the first and last positions of the target element.