First and last position of a value
Given an array nums sorted in non-decreasing order, which may contain repeated values, and
an integer target, return [first, last]: the first and last index at which
target appears. If it does not appear, return [−1, −1]. Use O(log n) comparisons.
Examples
| Input | Output | Why |
|---|---|---|
nums = [5, 7, 7, 8, 8, 10], target = 8 | [3, 4] | |
nums = [5, 7, 7, 8, 8, 10], target = 6 | [-1, -1] | |
nums = [], target = 0 | [-1, -1] |
Constraints
- 0 ≤ n ≤ 10⁵
- −10⁹ ≤ nums[i], target ≤ 10⁹
- nums is non-decreasing
Hints
Hint 1
Finding target and then walking left and right is O(n) when the whole array is one value.
Hint 2
The first occurrence is the first index with nums[i] ≥ target. Check that it holds target.
Hint 3
The last occurrence is one before the first index with nums[i] > target.
Where people lose marks
- Stopping the first search as soon as nums[mid] == target: that finds an occurrence, not the first one.
- Reading nums[first] when first = n.
- Forgetting the empty array, where both searches return 0 and nums[0] does not exist.
Dry run on [1,2,2,4,4,4,4,6,9],4
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 9target4
Two searches. The first finds where values ≥ 4 begin; the second where values > 4 begin.
-
Step 2 / 9searchlower boundlo0mid4hi9
lower bound: is a[4] = 4 ≥ 4? Yes: hi = 4.
-
Step 3 / 9searchlower boundlo0mid2hi4
lower bound: is a[2] = 2 ≥ 4? No: lo = 3.
-
Step 4 / 9searchlower boundlo3mid3hi4
lower bound: is a[3] = 4 ≥ 4? Yes: hi = 3.
-
Step 5 / 9first3
The lower bound is 3, and a[3] = 4 is the target, so it is present. Now the upper bound.
-
Step 6 / 9searchupper boundlo0mid4hi9
upper bound: is a[4] = 4 > 4? No: lo = 5.
-
Step 7 / 9searchupper boundlo5mid7hi9
upper bound: is a[7] = 6 > 4? Yes: hi = 7.
-
Step 8 / 9searchupper boundlo5mid6hi7
upper bound: is a[6] = 4 > 4? No: lo = 7.
-
Step 9 / 9answer[3, 6]
The run of 4s is [3, 7 − 1] = [3, 6]: 4 copies, found with 6 comparisons.
How to think about it
Searching for target and then scanning outwards is correct but costs O(n) when the run is long,
and an array of a million equal values is exactly the test an interviewer adds. Two boundary searches do it in
O(log n):
first= the first index withnums[i] ≥ target(the lower bound).end= the first index withnums[i] > target(the upper bound). The run is[first, end − 1].
If first = n or nums[first] ≠ target, the value is absent. Both searches are the same
function with a different test, which is why it pays to have one boundary search you can write without
thinking.
The upper bound minus the lower bound is the number of copies of target, in
O(log n) however many there are.
Complexity. Time O(log n) · Space O(1)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def search_range(nums, target):
def first_at_least(x): # first index with nums[i] >= x, or len(nums)
lo, hi = 0, len(nums)
while lo < hi:
mid = (lo + hi) // 2
if nums[mid] >= x:
hi = mid
else:
lo = mid + 1
return lo
first = first_at_least(target)
if first == len(nums) or nums[first] != target:
return [-1, -1]
return [first, first_at_least(target + 1) - 1] # integers: "> target" is ">= target + 1"#include <vector>
// first index with nums[i] >= x, or nums.size()
static int firstAtLeast(const std::vector<int>& nums, long long x) {
int lo = 0, hi = (int)nums.size();
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] >= x) hi = mid; else lo = mid + 1;
}
return lo;
}
std::vector<int> solve(const std::vector<int>& nums, int target) {
int first = firstAtLeast(nums, target);
if (first == (int)nums.size() || nums[first] != target) return {-1, -1};
return {first, firstAtLeast(nums, (long long)target + 1) - 1}; // target + 1 cannot overflow
}class Solution {
// first index with nums[i] >= x, or nums.length
private int firstAtLeast(int[] nums, long x) {
int lo = 0, hi = nums.length;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] >= x) hi = mid; else lo = mid + 1;
}
return lo;
}
public int[] searchRange(int[] nums, int target) {
int first = firstAtLeast(nums, target);
if (first == nums.length || nums[first] != target) return new int[]{-1, -1};
return new int[]{first, firstAtLeast(nums, (long) target + 1) - 1};
}
}function searchRange(nums, target) {
// first index with nums[i] >= x, or nums.length
const firstAtLeast = x => {
let lo = 0, hi = nums.length;
while (lo < hi) {
const mid = lo + ((hi - lo) >> 1);
if (nums[mid] >= x) hi = mid; else lo = mid + 1;
}
return lo;
};
const first = firstAtLeast(target);
if (first === nums.length || nums[first] !== target) return [-1, -1];
return [first, firstAtLeast(target + 1) - 1];
}fn solve(nums: &[i64], target: i64) -> Vec<i64> {
// partition_point returns the first index where the predicate turns false
let first = nums.partition_point(|&x| x < target);
if first == nums.len() || nums[first] != target {
return vec![-1, -1];
}
let end = nums.partition_point(|&x| x <= target);
vec![first as i64, end as i64 - 1]
}import Data.Array
solve :: [Int] -> Int -> [Int]
solve nums target
| first == n || a ! first /= target = [-1, -1]
| otherwise = [first, firstAtLeast (target + 1) - 1]
where
n = length nums
a = listArray (0, n - 1) nums
first = firstAtLeast target
-- first index with a[i] >= x, or n
firstAtLeast x = go 0 n
where
go lo hi
| lo >= hi = lo
| a ! mid >= x = go lo mid
| otherwise = go (mid + 1) hi
where mid = lo + (hi - lo) `div` 2
Variants to try
- Count the occurrences of every value in a list of queries: two bounds per query, O(q log n).
- Count the values between a and b inclusive: upper bound of b minus lower bound of a.
- The array is rotated: find the rotation point first, then search the correct half.
Next problems
Where a value belongs in a sorted array
Return the first position whose element is at least the target: the lower bound, which answers both "is it here" and "where would it go".
Open the problem → Medium20 minCount the partners that clear a threshold
Sort one list once; each query then becomes one lower-bound search for the smallest partner that is good enough.
Open the problem → Medium15 minResearch impact from sorted citation counts
The test compares each count with how many papers lie to its right; that test is monotone, so a lower bound finds the h-index.
Open the problem →Write a solution and run it against the real test table.
Keep going
Where a value belongs in a sorted array
Return the first position whose element is at least the target: the lower bound, which answers both "is it here" and "where would it go".
Open the problem → Medium20 minCount the partners that clear a threshold
Sort one list once; each query then becomes one lower-bound search for the smallest partner that is good enough.
Open the problem → Medium15 minResearch impact from sorted citation counts
The test compares each count with how many papers lie to its right; that test is monotone, so a lower bound finds the h-index.
Open the problem → Binary searchThe pattern behind it
Search the answer, not the array: halve a monotone predicate until one candidate is left.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.