{x}
blog image

Find First and Last Position of Element in Sorted Array

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

Solution Explanation: Find First and Last Position of Element in Sorted Array

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.

  1. 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.

  2. 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.

  3. 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.