34. Find the first and last position of an element in a sorted array
Given an integer array nums arranged in ascending order and a target value target. Find the start and end positions of the given target value in the array.
If the target value target does not exist in the array, return [- 1, - 1].
Advanced: can you design and implement an algorithm with time complexity of O(log n) to solve this problem?
Example 1:
- Input: num = [5,7,7,8,8,10], target = 8
- Output: [3,4]
Example 2:
- Input: num = [5,7,7,8,8,10], target = 6
- Output: [- 1, - 1]
Example 3:
- Input: num = [], target = 0
- Output: [- 1, - 1]
thinking
If the foundation of this topic is not very good, it is not recommended that you look at the short code. The short code hides too much logic. As a result, you confused the topic AC, but did not think about the specific details!
For students who don't know about dichotomy, do these two questions first:
Now let me discuss everything.
There are three cases to find the left and right boundaries of target in the array:
- Case 1: the target is on the right or left of the array range. For example, array {3,4,5}, target is 2 or array {3,4,5}, target is 6. At this time, {- 1, - 1} should be returned
- Case 2: if the target is in the array range and there is no target in the array, such as array {3,6,7}, and the target is 5, then {- 1, - 1} should be returned
- Case 3: if the target is in the array range and there is a target in the array, such as array {3,6,7}, and the target is 6, then {1,1} should be returned
These three situations are taken into account and the explanation is very clear.
Next, I'm looking for the left boundary and the right boundary.
Use the dichotomy to find the left and right boundaries. In order to make the code clear, I write two dichotomy to find the left and right boundaries respectively.
Students who have just come into contact with binary search are not recommended to come up. For example, if you use a binary to find the left and right boundaries, it is easy to surround yourself. It is recommended to write two binary points to find the left and right boundaries respectively
Find right boundary
Let's find the right boundary first. As for the binary search, if you've seen 704 You will know when to use while (left < = right) and when to use while (left < right) in binary search. In fact, as long as you know the loop invariant, it is easy to distinguish the two writing methods.
Then I use the while (left < = right) writing method here, and the interval is defined as [left, right], that is, the left closed and closed interval (if I can't understand it here, I strongly recommend reading the article 704. Binary search first, and then doing this topic after doing the 704 topic is much better)
OK: the calculated right boundary is the right boundary that does not cover the target, and the left boundary is the same.
You can write the following code
// Binary search to find the right boundary of target (excluding target) // If rightBorder is not assigned (i.e. target is on the left of the array range, such as array [3,3], target is 2), in order to process case 1 int getRightBorder(vector<int>& nums, int target) { int left = 0; int right = nums.size() - 1; // Define that the target is in the left closed and right closed interval, [left, right] int rightBorder = -2; // Record that rightBorder is not assigned while (left <= right) { // When left==right, the interval [left, right] is still valid int middle = left + ((right - left) / 2);// Preventing overflow is equivalent to (left + right)/2 if (nums[middle] > target) { right = middle - 1; // target is in the left interval, so [left, middle - 1] } else { // When num [middle] = = target, update left to get the right boundary of target left = middle + 1; rightBorder = left; } } return rightBorder; }
Find left boundary
// Binary search to find the left border of target (excluding target) // If leftBorder is not assigned (i.e. target is on the right side of the array range, for example, array [3,3],target is 4), in order to process case 1 int getLeftBorder(vector<int>& nums, int target) { int left = 0; int right = nums.size() - 1; // Define that the target is in the left closed and right closed interval, [left, right] int leftBorder = -2; // Record that leftBorder is not assigned while (left <= right) { int middle = left + ((right - left) / 2); if (nums[middle] >= target) { // To find the left boundary, update right when num [middle] = = target right = middle - 1; leftBorder = right; } else { left = middle + 1; } } return leftBorder; }
Deal with three situations
After calculating the left and right boundaries, take a look at the main body code, which covers the three cases discussed above
class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { int leftBorder = getLeftBorder(nums, target); int rightBorder = getRightBorder(nums, target); // Situation 1 if (leftBorder == -2 || rightBorder == -2) return {-1, -1}; // Situation III if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1}; // Situation II return {-1, -1}; } private: int getRightBorder(vector<int>& nums, int target) { int left = 0; int right = nums.size() - 1; int rightBorder = -2; // Record that rightBorder is not assigned while (left <= right) { int middle = left + ((right - left) / 2); if (nums[middle] > target) { right = middle - 1; } else { // Find the right boundary, and update the left when num [middle] = = target left = middle + 1; rightBorder = left; } } return rightBorder; } int getLeftBorder(vector<int>& nums, int target) { int left = 0; int right = nums.size() - 1; int leftBorder = -2; // Record that leftBorder is not assigned while (left <= right) { int middle = left + ((right - left) / 2); if (nums[middle] >= target) { // Find the left boundary, and update right when num [middle] = = target right = middle - 1; leftBorder = right; } else { left = middle + 1; } } return leftBorder; } };
This code has a large optimization space in simplicity, such as merging the left and right interval functions.
But the disassembly is clearer, and the three cases and the corresponding processing logic are fully displayed.
summary
Beginners suggest that you separate this topic one by one. As described in this problem solution, after thinking about the three situations, first focus on finding the right interval, then focus on finding the left interval, and make the final judgment according to the left and right intervals.
Don't come up and think that if we look for the left and right ranges together, we will take care of one and lose the other, and we can't pull it out.
Other language versions
Java
class Solution { int[] searchRange(int[] nums, int target) { int leftBorder = getLeftBorder(nums, target); int rightBorder = getRightBorder(nums, target); // Situation 1 if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1}; // Situation III if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1}; // Situation II return new int[]{-1, -1}; } int getRightBorder(int[] nums, int target) { int left = 0; int right = nums.length - 1; int rightBorder = -2; // Record that rightBorder is not assigned while (left <= right) { int middle = left + ((right - left) / 2); if (nums[middle] > target) { right = middle - 1; } else { // Find the right boundary, and update the left when num [middle] = = target left = middle + 1; rightBorder = left; } } return rightBorder; } int getLeftBorder(int[] nums, int target) { int left = 0; int right = nums.length - 1; int leftBorder = -2; // Record that leftBorder is not assigned while (left <= right) { int middle = left + ((right - left) / 2); if (nums[middle] >= target) { // Find the left boundary, and update right when num [middle] = = target right = middle - 1; leftBorder = right; } else { left = middle + 1; } } return leftBorder; } }
// Solution 2 // 1. First, binary search target in num array; // 2. If binary search fails, binarySearch returns - 1, indicating that there is no target in nums. At this time, searchRange directly returns {- 1, - 1}; // 3. If the binary search is successful, binarySearch returns a subscript with the value of num as target. Then, slide the pointer left and right to find the interval that meets the meaning of the question class Solution { public int[] searchRange(int[] nums, int target) { int index = binarySearch(nums, target); // Binary search if (index == -1) { // target does not exist in num, directly return {- 1, - 1} return new int[] {-1, -1}; // Anonymous Array } // If there is a targe in nums, slide the pointer left and right to find the interval that meets the meaning of the question int left = index; int right = index; // Slide left to find the left boundary while (left - 1 >= 0 && nums[left - 1] == nums[index]) { // Prevent array out of bounds. Logic short circuit, the sequence of two conditions cannot be changed left--; } // Slide left to find the right boundary while (right + 1 < nums.length && nums[right + 1] == nums[index]) { // Prevent array out of bounds. right++; } return new int[] {left, right}; } /** * Binary search * @param nums * @param target */ public int binarySearch(int[] nums, int target) { int left = 0; int right = nums.length - 1; // Invariant: left closed right closed interval while (left <= right) { // Invariant: left closed right closed interval int mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; // Invariant: left closed right closed interval } } return -1; // non-existent } }
Python
class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: def getRightBorder(nums:List[int], target:int) -> int: left, right = 0, len(nums)-1 rightBoder = -2 # Record that rightBorder is not assigned while left <= right: middle = left + (right-left) // 2 if nums[middle] > target: right = middle - 1 else: # Find the right boundary, and update the left when num [middle] = = target left = middle + 1 rightBoder = left return rightBoder def getLeftBorder(nums:List[int], target:int) -> int: left, right = 0, len(nums)-1 leftBoder = -2 # Record that leftBorder is not assigned while left <= right: middle = left + (right-left) // 2 if nums[middle] >= target: # Find the left boundary, and update right when num [middle] = = target right = middle - 1; leftBoder = right; else: left = middle + 1 return leftBoder leftBoder = getLeftBorder(nums, target) rightBoder = getRightBorder(nums, target) # Situation 1 if leftBoder == -2 or rightBoder == -2: return [-1, -1] # Situation III if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1] # Situation II return [-1, -1]
# Solution 2 # 1. First, binary search target in num array; # 2. If binary search fails, binarySearch returns - 1, indicating that there is no target in nums. At this time, searchRange directly returns {- 1, - 1}; # 3. If the binary search is successful, binarySearch returns a subscript with the value of num as target. Then, slide the pointer left and right to find the interval that meets the meaning of the question class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: def binarySearch(nums:List[int], target:int) -> int: left, right = 0, len(nums)-1 while left<=right: # Invariant: left closed right closed interval middle = left + (right-left) // 2 if nums[middle] > target: right = middle - 1 elif nums[middle] < target: left = middle + 1 else: return middle return -1 index = binarySearch(nums, target) if index == -1:return [-1, -1] # target does not exist in num, directly return {- 1, - 1} # If there is a targe in nums, slide the pointer left and right to find the interval that meets the meaning of the question left, right = index, index # Slide left to find the left boundary while left -1 >=0 and nums[left - 1] == target: left -=1 # Slide right to find the right boundary while right+1 < len(nums) and nums[right + 1] == target: right +=1 return [left, right]
# Solution 3 # 1. Firstly, binary search in the num array to get the first subscript greater than or equal to target (left boundary) and the first subscript greater than target (right boundary); # 2. If left boundary < = right boundary, return to [left boundary, right boundary]. Otherwise [- 1, - 1] class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: def binarySearch(nums:List[int], target:int, lower:bool) -> int: left, right = 0, len(nums)-1 ans = len(nums) while left<=right: # Invariant: left closed right closed interval middle = left + (right-left) //2 # lower is True. Execute the first half and find the first subscript greater than or equal to target. Otherwise, find the first subscript greater than or equal to target if nums[middle] > target or (lower and nums[middle] >= target): right = middle - 1 ans = middle else: left = middle + 1 return ans leftBorder = binarySearch(nums, target, True) # Search left boundary rightBorder = binarySearch(nums, target, False) -1 # Search right boundary if leftBorder<= rightBorder and rightBorder< len(nums) and nums[leftBorder] == target and nums[rightBorder] == target: return [leftBorder, rightBorder] return [-1, -1]
# Solution 4 # 1. Firstly, the first subscript leftBorder greater than or equal to target is obtained by binary search in the num array; # 2. The first subscript greater than or equal to target+1 is obtained by binary search in the num array, and the rightBorder is obtained by subtracting 1; # 3. If the start position is on the right of the array or there is no target, then [- 1, - 1] is returned. Otherwise, return to [leftBorder, rightBorder] class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: def binarySearch(nums:List[int], target:int) -> int: left, right = 0, len(nums)-1 while left<=right: # Invariant: left closed right closed interval middle = left + (right-left) //2 if nums[middle] >= target: right = middle - 1 else: left = middle + 1 return left # If target exists, the first value equal to target is returned leftBorder = binarySearch(nums, target) # Search left boundary rightBorder = binarySearch(nums, target+1) -1 # Search right boundary if leftBorder == len(nums) or nums[leftBorder]!= target: # Cases I and II return [-1, -1] return [leftBorder, rightBorder]