This problem looks like ordinary binary search, but one match is not enough. We need the full boundary of the target block, so we intentionally bias one search left and another search right.
Problem 1: Find First and Last Position of Element in Sorted Array
Problem description:
Given a sorted array nums and a target value, return the first and last indices where the target appears. If the target does not exist, return [-1, -1].
What we are solving actually: Normal binary search can tell us that the target exists, but it does not guarantee the leftmost or rightmost position. The hidden task is really two boundary queries: “where does the target block start?” and “where does it end?”
What we are doing actually:
- Run one binary search that keeps searching left after a match to find the first index.
- If the target is absent, return
[-1, -1]immediately. - Run another binary search that keeps searching right after a match to find the last index.
- Return both boundaries.
class Solution {
public int[] searchRange(int[] nums, int target) {
int first = firstIndex(nums, target);
if (first == -1) return new int[]{-1, -1};
int last = lastIndex(nums, target);
return new int[]{first, last};
}
private int firstIndex(int[] nums, int target) {
int left = 0, right = nums.length - 1, ans = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1; // Keep searching left because a boundary might exist earlier.
} else {
left = mid + 1;
}
if (nums[mid] == target) {
ans = mid; // Record match, but do not stop; we still want the leftmost one.
}
}
return ans;
}
private int lastIndex(int[] nums, int target) {
int left = 0, right = nums.length - 1, ans = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1; // Keep searching right because a later boundary might exist.
} else {
right = mid - 1;
}
if (nums[mid] == target) {
ans = mid; // Record match, but do not stop; we still want the rightmost one.
}
}
return ans;
}
}
Debug steps:
- print
left,mid,right, andansduring both searches - test
[5,7,7,8,8,10]with targets8and6 - verify the invariant that
ansstores the best boundary found so far while the search continues in the needed direction
Why Two Binary Searches
A plain binary search stops as soon as it finds any matching index. That is not enough here because duplicates create a whole block of equal values.
So we split the problem:
- first index search = left-biased search
- last index search = right-biased search
This separation keeps the logic clear and prevents tricky mixed-branch bugs.
Dry Run
Input: nums = [5,7,7,8,8,10], target = 8
First-index search:
mid=2, value7-> move rightmid=4, value8-> recordans=4, keep searching leftmid=3, value8-> recordans=3, keep searching left
First index = 3
Last-index search:
mid=2, value7-> move rightmid=4, value8-> recordans=4, keep searching rightmid=5, value10-> move left
Last index = 4
Answer: [3, 4]
Why Not Stop on First Match
Stopping on the first match gives the wrong answer when duplicates exist.
Example:
- array:
[2,2,2,2] - target:
2
Any random match is not enough. We need the full range:
- first =
0 - last =
3
That is why the searches continue even after a match is found.
Alternative Bound Formulation
Another common approach is:
left = lowerBound(target)right = lowerBound(target + 1) - 1
That version is elegant and generalizes well. The explicit first/last search approach is often easier to teach because the directional bias is visible in the code.
Common Mistakes
- returning immediately when
nums[mid] == target - trying to reuse one generic binary-search function without clear boundary rules
- forgetting to handle the target-absent case
- mixing left-biased and right-biased updates incorrectly
Boundary Cases
- empty array ->
[-1, -1] - target absent ->
[-1, -1] - one occurrence -> both indices are the same
- all elements equal to target -> answer is full array range
- target at first or last position -> still handled cleanly
Complexity
- Time:
O(log n) - Space:
O(1)
Key Takeaways
- this is really two boundary binary searches, not one ordinary search
- after a match, keep searching in the boundary direction you care about
- duplicate-heavy tests are the best way to catch logic bugs here
Comments