Editorial
Core insight
Find First and Last Position of Element in Sorted Array · Binary Search Patterns
Core Insight for Find First and Last Position of Element in Sorted Array
The standard binary search algorithm returns the index of any occurrence of the target. However, it does not guarantee finding the first or last occurrence when duplicates exist.
To solve LeetCode 34 efficiently, we must recognize that finding the "first" position and the "last" position are two separate sub-problems that can be solved using the same logic with a slight variation.
The Key Invariant:
When we find nums[mid] == target, we have found an instance of the target, but we do not stop.
- To find the First Position: We record the current
midas a potential answer but assume the true first position might lie to the left. Therefore, we narrow the search space to[low, mid - 1]. - To find the Last Position: We record the current
midas a potential answer but assume the true last position might lie to the right. Therefore, we narrow the search space to[mid + 1, high].
Visual Description: Imagine the array as a series of blocks. The binary search repeatedly cuts the search space in half.
- In the first pass, even if the algorithm lands on the target, it treats the right half as "discardable" to push the search pointer as far left as possible.
- In the second pass, it treats the left half as "discardable" upon finding a match to push the search pointer as far right as possible.
The search terminates only when the
lowandhighpointers cross, ensuring the boundary has been identified precisely.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 34: Find First and Last Position of Element in Sorted Array Solution & Explanation
Problem Overview
TL;DR: To solve this in logarithmic time, perform two modified binary searches: one to locate the leftmost index of the target and another to locate the rightmost index.
The problem asks us to find the starting and ending indices of a specific target value within an array of integers sorted in non-decreasing order. If the target does not exist in the array, we must return [-1, -1]. This is the core challenge of LeetCode 34: Find First and Last Position of Element in Sorted Array, a classic interview question that tests the ability to modify standard search algorithms.
The critical constraint is that the algorithm must run in O(log n) time complexity. This immediately rules out linear scanning approaches.
Brute Force Approach for Find First and Last Position of Element in Sorted Array
The naive approach involves iterating through the array linearly to find the target.
- Initialize
firstandlastto -1. - Iterate through the array from index
0ton-1. - The first time we encounter
target, setfirstto the current index. - Every time we encounter
target, updatelastto the current index (since the array is sorted, subsequent occurrences are always to the right). - Return
[first, last].
Pseudo-code:
function bruteForce(nums, target):
first = -1
last = -1
for i from 0 to length(nums) - 1:
if nums[i] == target:
if first == -1:
first = i
last = i
return [first, last]Time Complexity Analysis: The time complexity is O(n), where n is the number of elements in the array. In the worst case, we must traverse the entire array.
Why it fails: While this solution produces the correct output, it violates the explicit problem constraint requiring O(log n) runtime complexity. In a large dataset (up to elements), an O(n) solution is significantly slower than O(log n), and in an interview context, this solution would be considered suboptimal.
Algorithm Strategy: Binary Search Patterns
We will implement a helper function (or run the same logic twice) that performs a binary search. This function will take a parameter indicating whether we are searching for the first or last occurrence.
Strategy Steps:
- Initialize Pointers: Set
low = 0andhigh = n - 1. - Maintain Candidate: Create a variable
index = -1to store the most recent index wherenums[mid] == target. - Loop: While
low <= high:- Calculate
mid. - If
nums[mid] < target: Move right (low = mid + 1). - If
nums[mid] > target: Move left (high = mid - 1). - If
nums[mid] == target:- Update
index = mid. - Crucial Branch:
- If searching for First Position, continue searching left (
high = mid - 1). - If searching for Last Position, continue searching right (
low = mid + 1).
- If searching for First Position, continue searching left (
- Update
- Calculate
- Execute: Run this search twice. Once to find the start, once to find the end.
- Return: The pair of indices found.
Execution Flow
Let's trace the algorithm with nums = [5, 7, 7, 8, 8, 10] and target = 8.
Pass 1: Find First Position
low=0,high=5,mid=2.nums[2]is 7. , solowbecomes 3.low=3,high=5,mid=4.nums[4]is 8.- Target found. Store
index = 4. - We want the first position, so we look left.
highbecomes 3.
- Target found. Store
low=3,high=3,mid=3.nums[3]is 8.- Target found. Store
index = 3. - We want the first position, so we look left.
highbecomes 2.
- Target found. Store
low=3,high=2. Loop terminates. First Position = 3.
Pass 2: Find Last Position
low=0,high=5,mid=2.nums[2]is 7. , solowbecomes 3.low=3,high=5,mid=4.nums[4]is 8.- Target found. Store
index = 4. - We want the last position, so we look right.
lowbecomes 5.
- Target found. Store
low=5,high=5,mid=5.nums[5]is 10. , sohighbecomes 4.low=5,high=4. Loop terminates. Last Position = 4.
Result: [3, 4].
Proof of Correctness
The correctness relies on the monotonicity of the sorted array.
- Termination: The
while (low <= high)loop is guaranteed to terminate because the search space reduces by roughly half in each iteration, eventually causing pointers to cross. - Completeness: If the target exists,
nums[mid] == targetwill eventually be true. - Boundary Finding: By strictly moving
hightomid - 1after a match (when finding the first), we ensure that we never settle for a non-minimal index. We exhaustively verify that no target exists to the left of the current candidate. The symmetric logic applies to finding the last index.
Pattern Reuse Notes
The Binary Search - Find First/Last Occurrence pattern logic is directly applicable to several other problems. Understanding how to continue searching after a match is key to solving:
- LeetCode 658: Find K Closest Elements - Requires finding the "closest" element, which often involves finding the first element target (lower bound).
- LeetCode 35: Search Insert Position - This is essentially finding the "first" index where the value is greater than or equal to the target.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 34
1class Solution {
2public:
3 vector<int> searchRange(vector<int>& nums, int target) {
4 vector<int> result = {-1, -1};
5 result[0] = findBound(nums, target, true); // Find first
6 if (result[0] == -1) return result; // Target not found
7 result[1] = findBound(nums, target, false); // Find last
8 return result;
9 }
10
11private:
12 int findBound(vector<int>& nums, int target, bool isFirst) {
13 int left = 0, right = nums.size() - 1;
14 int boundIndex = -1;
15
16 while (left <= right) {
17 int mid = left + (right - left) / 2;
18
19 if (nums[mid] == target) {
20 boundIndex = mid;
21 // If finding first, narrow search to left half
22 if (isFirst) {
23 right = mid - 1;
24 }
25 // If finding last, narrow search to right half
26 else {
27 left = mid + 1;
28 }
29 } else if (nums[mid] < target) {
30 left = mid + 1;
31 } else {
32 right = mid - 1;
33 }
34 }
35 return boundIndex;
36 }
37};Java Solution for LeetCode 34
1class Solution {
2 public int[] searchRange(int[] nums, int target) {
3 int[] result = new int[]{-1, -1};
4 result[0] = findBound(nums, target, true);
5
6 // If the target isn't found once, it won't be found twice
7 if (result[0] == -1) {
8 return result;
9 }
10
11 result[1] = findBound(nums, target, false);
12 return result;
13 }
14
15 private int findBound(int[] nums, int target, boolean isFirst) {
16 int left = 0;
17 int right = nums.length - 1;
18 int index = -1;
19
20 while (left <= right) {
21 int mid = left + (right - left) / 2;
22
23 if (nums[mid] == target) {
24 index = mid;
25 if (isFirst) {
26 right = mid - 1; // Continue searching left
27 } else {
28 left = mid + 1; // Continue searching right
29 }
30 } else if (nums[mid] < target) {
31 left = mid + 1;
32 } else {
33 right = mid - 1;
34 }
35 }
36
37 return index;
38 }
39}Python Solution for LeetCode 34
1class Solution:
2 def searchRange(self, nums: List[int], target: int) -> List[int]:
3
4 def find_bound(is_first: bool) -> int:
5 left, right = 0, len(nums) - 1
6 bound_index = -1
7
8 while left <= right:
9 mid = left + (right - left) // 2
10
11 if nums[mid] == target:
12 bound_index = mid
13 if is_first:
14 right = mid - 1 # Narrow search to left
15 else:
16 left = mid + 1 # Narrow search to right
17 elif nums[mid] < target:
18 left = mid + 1
19 else:
20 right = mid - 1
21
22 return bound_index
23
24 start = find_bound(True)
25 if start == -1:
26 return [-1, -1]
27
28 end = find_bound(False)
29 return [start, end]