Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Binary Search - Finding elements in sorted arrays with O(log n) time complexity
Lower Bound / Upper Bound - Modifying binary search to find the first or last occurrence of a target
Sorted Array Properties - Understanding that duplicates appear consecutively in sorted arrays
1. Brute Force
Intuition
Since the array is sorted, all occurrences of the target will be consecutive. We can scan through the array once, recording the first and last positions where we encounter the target. The first time we see the target, we set both the start and end to that index. For subsequent matches, we only update the end position.
Algorithm
Initialize result array res = [-1, -1].
Iterate through the array with index i:
If nums[i] equals the target:
If res[0] is still -1, set both res[0] and res[1] to i.
classSolution:defsearchRange(self, nums: List[int], target:int)-> List[int]:
res =[-1,-1]for i, num inenumerate(nums):if num == target:if res[0]==-1:
res[0]= res[1]= i
else:
res[1]= i
return res
publicclassSolution{publicint[]searchRange(int[] nums,int target){int[] res ={-1,-1};for(int i =0; i < nums.length; i++){if(nums[i]== target){if(res[0]==-1){
res[0]= res[1]= i;}else{
res[1]= i;}}}return res;}}
classSolution{public:
vector<int>searchRange(vector<int>& nums,int target){
vector<int> res ={-1,-1};for(int i =0; i < nums.size(); i++){if(nums[i]== target){if(res[0]==-1){
res[0]= res[1]= i;}else{
res[1]= i;}}}return res;}};
classSolution{/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/searchRange(nums, target){let res =[-1,-1];for(let i =0; i < nums.length; i++){if(nums[i]=== target){if(res[0]===-1){
res[0]= res[1]= i;}else{
res[1]= i;}}}return res;}}
publicclassSolution{publicint[]SearchRange(int[] nums,int target){int[] res =newint[]{-1,-1};for(int i =0; i < nums.Length; i++){if(nums[i]== target){if(res[0]==-1){
res[0]= i;
res[1]= i;}else{
res[1]= i;}}}return res;}}
funcsearchRange(nums []int, target int)[]int{
res :=[]int{-1,-1}for i, num :=range nums {if num == target {if res[0]==-1{
res[0]= i
res[1]= i
}else{
res[1]= i
}}}return res
}
class Solution {funsearchRange(nums: IntArray, target: Int): IntArray {val res =intArrayOf(-1,-1)for(i in nums.indices){if(nums[i]== target){if(res[0]==-1){
res[0]= i
res[1]= i
}else{
res[1]= i
}}}return res
}}
classSolution{funcsearchRange(_ nums:[Int],_ target:Int)->[Int]{var res =[-1,-1]for i in0..<nums.count {if nums[i]== target {if res[0]==-1{
res[0]= i
res[1]= i
}else{
res[1]= i
}}}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1)
2. Binary Search - I
Intuition
Binary search can find any occurrence of the target in O(log n) time, but we need both the first and last occurrences. The key insight is that when we find the target, instead of returning immediately, we continue searching. For the leftmost occurrence, we search the left half after finding a match. For the rightmost, we search the right half. This gives us two separate binary searches with a bias parameter.
Algorithm
Implement a binary search helper that takes a leftBias parameter.
When nums[m] == target:
Record index m as a candidate.
If leftBias is true, continue searching left (r = m - 1).
Otherwise, continue searching right (l = m + 1).
Call the helper twice: once with leftBias = true for the start position, and once with leftBias = false for the end position.
classSolution:defsearchRange(self, nums: List[int], target:int)-> List[int]:
left = self.binarySearch(nums, target,True)
right = self.binarySearch(nums, target,False)return[left, right]defbinarySearch(self, nums, target, leftBias):
l, r =0,len(nums)-1
i =-1while l <= r:
m =(l + r)//2if target > nums[m]:
l = m +1elif target < nums[m]:
r = m -1else:
i = m
if leftBias:
r = m -1else:
l = m +1return i
publicclassSolution{publicint[]searchRange(int[] nums,int target){int left =binarySearch(nums, target,true);int right =binarySearch(nums, target,false);returnnewint[]{left, right};}privateintbinarySearch(int[] nums,int target,boolean leftBias){int l =0, r = nums.length -1, i =-1;while(l <= r){int m =(l + r)/2;if(target > nums[m]){
l = m +1;}elseif(target < nums[m]){
r = m -1;}else{
i = m;if(leftBias){
r = m -1;}else{
l = m +1;}}}return i;}}
classSolution{public:
vector<int>searchRange(vector<int>& nums,int target){int left =binarySearch(nums, target,true);int right =binarySearch(nums, target,false);return{left, right};}private:intbinarySearch(vector<int>& nums,int target,bool leftBias){int l =0, r = nums.size()-1, i =-1;while(l <= r){int m =(l + r)/2;if(target > nums[m]){
l = m +1;}elseif(target < nums[m]){
r = m -1;}else{
i = m;if(leftBias){
r = m -1;}else{
l = m +1;}}}return i;}};
classSolution{/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/searchRange(nums, target){let left =this.binarySearch(nums, target,true);let right =this.binarySearch(nums, target,false);return[left, right];}/**
* @param {number[]} nums
* @param {number} target
* @param {boolean} leftBias
* @return {number}
*/binarySearch(nums, target, leftBias){let l =0,
r = nums.length -1,
i =-1;while(l <= r){let m = Math.floor((l + r)/2);if(target > nums[m]){
l = m +1;}elseif(target < nums[m]){
r = m -1;}else{
i = m;if(leftBias){
r = m -1;}else{
l = m +1;}}}return i;}}
publicclassSolution{publicint[]SearchRange(int[] nums,int target){int left =BinarySearch(nums, target,true);int right =BinarySearch(nums, target,false);returnnewint[]{ left, right };}privateintBinarySearch(int[] nums,int target,bool leftBias){int l =0, r = nums.Length -1, i =-1;while(l <= r){int m = l +(r - l)/2;if(target > nums[m]){
l = m +1;}elseif(target < nums[m]){
r = m -1;}else{
i = m;if(leftBias){
r = m -1;}else{
l = m +1;}}}return i;}}
funcsearchRange(nums []int, target int)[]int{
left :=binarySearch(nums, target,true)
right :=binarySearch(nums, target,false)return[]int{left, right}}funcbinarySearch(nums []int, target int, leftBias bool)int{
l, r, i :=0,len(nums)-1,-1for l <= r {
m :=(l + r)/2if target > nums[m]{
l = m +1}elseif target < nums[m]{
r = m -1}else{
i = m
if leftBias {
r = m -1}else{
l = m +1}}}return i
}
class Solution {funsearchRange(nums: IntArray, target: Int): IntArray {val left =binarySearch(nums, target,true)val right =binarySearch(nums, target,false)returnintArrayOf(left, right)}privatefunbinarySearch(nums: IntArray, target: Int, leftBias: Boolean): Int {var l =0var r = nums.size -1var i =-1while(l <= r){val m =(l + r)/2if(target > nums[m]){
l = m +1}elseif(target < nums[m]){
r = m -1}else{
i = m
if(leftBias){
r = m -1}else{
l = m +1}}}return i
}}
classSolution{funcsearchRange(_ nums:[Int],_ target:Int)->[Int]{letleft=binarySearch(nums, target,true)letright=binarySearch(nums, target,false)return[left,right]}privatefuncbinarySearch(_ nums:[Int],_ target:Int,_ leftBias:Bool)->Int{var l =0var r = nums.count -1var i =-1while l <= r {let m =(l + r)/2if target > nums[m]{
l = m +1}elseif target < nums[m]{
r = m -1}else{
i = m
if leftBias {
r = m -1}else{
l = m +1}}}return i
}}
Time & Space Complexity
Time complexity: O(logn)
Space complexity: O(1)
3. Binary Search - II
Intuition
We can use a single style of binary search that finds the insertion point for a value. The insertion point for target gives us the first occurrence (if it exists). The insertion point for target + 1 gives us one past the last occurrence. This approach uses the standard lower-bound binary search pattern, which finds the smallest index where nums[index] >= target.
Algorithm
Implement a binary search that finds the leftmost position where nums[m] >= target.
Find start = binarySearch(target).
If start equals the array length or nums[start] != target, return [-1, -1].
classSolution:defsearchRange(self, nums: List[int], target:int)-> List[int]:
n =len(nums)defbinarySearch(target):
l, r =0, n
while l < r:
m = l +(r - l)//2if nums[m]>= target:
r = m
else:
l = m +1return l
start = binarySearch(target)if start == n or nums[start]!= target:return[-1,-1]return[start, binarySearch(target +1)-1]
publicclassSolution{publicint[]searchRange(int[] nums,int target){int n = nums.length;int start =binarySearch(nums, target, n);if(start == n || nums[start]!= target){returnnewint[]{-1,-1};}returnnewint[]{start,binarySearch(nums, target +1, n)-1};}privateintbinarySearch(int[] nums,int target,int n){int l =0, r = n;while(l < r){int m = l +(r - l)/2;if(nums[m]>= target){
r = m;}else{
l = m +1;}}return l;}}
classSolution{public:
vector<int>searchRange(vector<int>& nums,int target){int n = nums.size();int start =binarySearch(nums, target, n);if(start == n || nums[start]!= target){return{-1,-1};}return{start,binarySearch(nums, target +1, n)-1};}private:intbinarySearch(vector<int>& nums,int target,int n){int l =0, r = n;while(l < r){int m = l +(r - l)/2;if(nums[m]>= target){
r = m;}else{
l = m +1;}}return l;}};
classSolution{/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/searchRange(nums, target){const n = nums.length;constbinarySearch=(target)=>{let l =0,
r = n;while(l < r){let m = Math.floor(l +(r - l)/2);if(nums[m]>= target){
r = m;}else{
l = m +1;}}return l;};let start =binarySearch(target);if(start === n || nums[start]!== target){return[-1,-1];}return[start,binarySearch(target +1)-1];}}
publicclassSolution{publicint[]SearchRange(int[] nums,int target){int n = nums.Length;intBinarySearch(int t){int l =0, r = n;while(l < r){int m = l +(r - l)/2;if(nums[m]>= t){
r = m;}else{
l = m +1;}}return l;}int start =BinarySearch(target);if(start == n || nums[start]!= target){returnnewint[]{-1,-1};}int end =BinarySearch(target +1)-1;returnnewint[]{ start, end };}}
funcsearchRange(nums []int, target int)[]int{
n :=len(nums)
binarySearch :=func(t int)int{
l, r :=0, n
for l < r {
m := l +(r-l)/2if nums[m]>= t {
r = m
}else{
l = m +1}}return l
}
start :=binarySearch(target)if start == n || nums[start]!= target {return[]int{-1,-1}}return[]int{start,binarySearch(target+1)-1}}
class Solution {funsearchRange(nums: IntArray, target: Int): IntArray {val n = nums.size
funbinarySearch(t: Int): Int {var l =0var r = n
while(l < r){val m = l +(r - l)/2if(nums[m]>= t){
r = m
}else{
l = m +1}}return l
}val start =binarySearch(target)if(start == n || nums[start]!= target){returnintArrayOf(-1,-1)}returnintArrayOf(start,binarySearch(target +1)-1)}}
classSolution{funcsearchRange(_ nums:[Int],_ target:Int)->[Int]{let n = nums.count
funcbinarySearch(_ t:Int)->Int{var l =0var r = n
while l < r {let m = l +(r - l)/2if nums[m]>= t {
r = m
}else{
l = m +1}}return l
}let start =binarySearch(target)if start == n || nums[start]!= target {return[-1,-1]}return[start,binarySearch(target +1)-1]}}
Time & Space Complexity
Time complexity: O(logn)
Space complexity: O(1)
4. In-Built Function
Intuition
Most programming languages provide built-in functions for binary search that find lower and upper bounds. These functions are optimized and well-tested, making them a practical choice when available. The lower bound gives the first occurrence, and the upper bound (minus one) gives the last occurrence.
Algorithm
Use the language's built-in lower-bound function to find the first position where the target could be inserted.
If this position is out of bounds or doesn't contain the target, return [-1, -1].
Use the upper-bound function to find the position just after the last occurrence.
Standard binary search returns immediately upon finding the target, but this gives an arbitrary occurrence, not the first or last. You must continue searching (leftward for first, rightward for last) even after finding a match to locate the boundary positions.
Not Handling the Target-Not-Found Case
After performing binary search for the first occurrence, you must verify that the found index actually contains the target. If the target does not exist in the array, the binary search returns an insertion point, not a valid position. Always check bounds and value equality before returning.
Off-by-One Errors in Boundary Calculation
When using lower-bound and upper-bound style searches, the upper bound points to one past the last occurrence. Forgetting to subtract 1 when computing the end position, or using incorrect loop conditions (l <= r vs l < r), leads to wrong indices or infinite loops.