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
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute-force method to find the first and last spot of a number in a sorted list is to simply look at each number in the list, one by one. We check each number to see if it is equal to the target number we are searching for.
Here's how the algorithm would work step-by-step:
def find_first_and_last_brute_force(sorted_numbers, target_number):
first_position = -1
last_position = -1
for index in range(len(sorted_numbers)):
# Check each number to see if it equals the target
if sorted_numbers[index] == target_number:
# If we haven't found the target yet, mark the first position
if first_position == -1:
first_position = index
last_position = index
# Otherwise, update the last position
else:
last_position = index
return [first_position, last_position]
Because the numbers are sorted, we can use a very efficient search strategy. Instead of checking each number individually, we repeatedly divide the search area in half to quickly narrow down the location of the number we are looking for and where it starts and ends.
Here's how the algorithm would work step-by-step:
def find_first_and_last_position(nums, target):
def find_first(nums, target):
left_index = 0
right_index = len(nums) - 1
first_position = -1
while left_index <= right_index:
middle_index = (left_index + right_index) // 2
if nums[middle_index] < target:
left_index = middle_index + 1
elif nums[middle_index] > target:
right_index = middle_index - 1
else:
# Potential first occurence found.
first_position = middle_index
# Search left side for the first.
right_index = middle_index - 1
return first_position
def find_last(nums, target):
left_index = 0
right_index = len(nums) - 1
last_position = -1
while left_index <= right_index:
middle_index = (left_index + right_index) // 2
if nums[middle_index] < target:
left_index = middle_index + 1
elif nums[middle_index] > target:
right_index = middle_index - 1
else:
# Potential last occurence found.
last_position = middle_index
# Search right side for the last.
left_index = middle_index + 1
return last_position
first_position = find_first(nums, target)
last_position = find_last(nums, target)
return [first_position, last_position]
Case | How to Handle |
---|---|
Empty array | Return [-1, -1] if the input array is empty as the target cannot exist. |
Target is smaller than the smallest element in the array | Binary search will not find the target and return [-1, -1]. |
Target is larger than the largest element in the array | Binary search will not find the target and return [-1, -1]. |
Array with one element and target matches the element | Return [0, 0] if the single element matches the target. |
Array with one element and target does not match the element | Return [-1, -1] if the single element does not match the target. |
Array with all elements equal to the target | Binary search to find the leftmost and rightmost indices will correctly identify the start and end positions. |
Target exists only once in the array | Binary search for leftmost and rightmost will return the same index for both, resulting in [index, index]. |
Large array sizes to consider potential time complexity bottlenecks | Binary search ensures a logarithmic time complexity, handling large array sizes efficiently. |