A good idea will be to understand this problem statement first. We are given a sorted array and a target element. We need to return the first and last index of this target element in the sorted array. The array has a 0
based indexing, and if the element is not found we need to return a [-1, -1]
.
Let us look at some sample test cases:
Input: [ 5, 7, 7, 8, 8, 10 ]
Target: 8
Output: [3, 4]
Explanation:
The first occurrence of element 8
is at index 3
and the last occurrence is at index 4
Input: [ 5, 7, 7, 8, 8, 10 ]
Target: 6
Output: [-1, -1]
Explanation:
We cannot find 6
in the array.
Brute Force Method:
The most naive way or the brute force method to solve this problem is very simple. We know the index of each element in the array. So we can devise a way to find the first and last occurrence.
- Start iterating through the array from the beginning and match each element to the target element.
- If the element matches, we get the first index. Mark it and move ahead.
- Keep matching the elements until we see a mismatch.
- This is the last index of the element.
As soon as you have a mismatch, just exit the loop iteration and we have our answer of the first and last index. But this approach is not scalable. In a worst case scenario, we will have to scan the entire array.
Efficient Solution:
In our previous approach, we did not use any extra space, and the time complexity is also O(n)
. When we think about improving it, we tend to find a logarithmic time complexity. This gives you some idea.
As soon as you see a sorted array, the first thing that should come to your mind is the binary search algorithm. We have to find the first and last index in a sorted array. This is same as searching for that element.
All we have to do is apply a modified version of the binary search algorithm. A traditional approach will be to search for the exact target element. But in this scenario we have to apply this search technique 2
times.
- Find the first occurrence by searching for a the target element such that the left element is smaller or null.
- Find the last occurrence by searching for a target element such that the element to right of it is greater or null.
For example, let us say we have an example array:[0, 0, 1, 1, 2, 3, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7]
Target element = 5
When we start applying binary search for 5
, the array will be divided into 2
halves.
The trick here is to now search for an occurrence of 5
with a smaller element to the left in the left half of the array.
Once we get this, repeat the same process to search for an occurrence of 5
with a larger element to the right in the other half.
This is another example of the Divide and Conquer algorithmic paradigm.
Code:
public int[] searchRange(int[] nums, int target) {
int left = findLeftBound(nums, target);
int right = findRightBound(nums, target);
return new int[]{left, right};
}
private int findLeftBound(int[] nums, int target) {
int index = -1, low = 0, high = nums.length - 1;
// Standard binary search
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] == target) {
index = mid;
high = mid - 1; // Look in the left sub-array
}
else if (nums[mid] < target)
low = mid + 1;
else
high = mid - 1;
}
return index;
}
private int findRightBound(int[] nums, int target) {
int index = -1, low = 0, high = nums.length - 1;
// Standard binary search
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] == target) {
index = mid;
low = mid + 1; // Look in the right sub-array
}
else if (nums[mid] < target)
low = mid + 1;
else
high = mid - 1;
}
return index;
}
Code language: Java (java)
Time Complexity: O(\log n)
Space Complexity: O(1)
You can also find the code and test cases on GitHub.