34. Find First and Last Position of Element in Sorted Array
Leetcode Array Binary SearchGiven an array of integers nums
sorted in ascending order, find the starting and ending position of a given target
value.
Your algorithm's runtime complexity must be in the order of O(\log n).
If the target is not found in the array, return [-1, -1]
.
Example 1:
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]
Example 2:
Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]
Python¶
在排序数组中查找元素的第一个和最后一个位置。由于数组是排序的,而且时间复杂度要求是O(\log n),最典型的就是二分查找了。最直接的方法是通过二分查找方法找到目标值所在的位置k,即nums[k] = target
,然后从k开始向左右扫描来定位左右边界。
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length == 0) return new int[]{-1, -1};
int n = nums.length;
int lo = 0, hi = nums.length - 1;
while (lo <= hi) {
int mid = lo + (hi - lo)/2;
int cmp = nums[mid] - target;
if (cmp > 0) hi = mid - 1;
else if (cmp < 0) lo = mid + 1;
else {
lo = mid; hi = mid;
while (lo >=0 && nums[lo] == nums[mid]) lo--;
while (hi < n && nums[hi] == nums[mid]) hi++;
return new int[]{lo + 1, hi - 1};
}
}
return new int[]{-1, -1};
}
def searchRange(self, nums, target):
n = len(nums)
left = 0
right = n - 1
result_left, result_right = -1, -1
while left <= right:
mid = (left + right) //2
print(left, right, mid)
if nums[mid] == target:
tmp = mid
while tmp > -1 and nums[tmp] == target:
tmp -= 1
result_left = tmp+1
tmp = mid
while tmp < n and nums[tmp] == target:
tmp += 1
result_right = tmp-1
return [result_left, result_right]
elif nums[mid] > target:
right = mid - 1
else:
left = mid + 1
return [-1, -1]
二分查找的时间复杂度是O(\log n),查找左右边界的时间复杂度是O(k),其中k是边界的长度。当所有元素的值都为目标值时,最坏时间复杂度是O(n)。不符合题目的时间复杂度要求。
一个更优化的方案是,在寻找左右边界时也采用二分查找的办法,具体方法是:先找到该值的位置find, 针对左边界,寻找[0, find]的区间,针对右边界,寻找[find, nums.length-1]区间,直到区间内找不到该值。
binarySearch()
方法使用二分查找算法查找区间内的目标值,如果查找到该值,则返回该值的位置;如果没有查找到该值,则返回小于该值的位置。
另外,特别需要注意程序中下标的溢出。
public int[] searchRange(int[] nums, int target) {
// starting and ending position of a given target value.
int left, right;
// 查找目标值
int find = binarySearch(nums, 0, nums.length - 1, target);
// 没有查找到目标值
if (find < 0 || nums[find] != target) return new int[]{-1, -1};
// 向左搜索目标值
int findLeft = find;
while (findLeft != 0 && nums[findLeft] == target)
findLeft = binarySearch(nums, 0, findLeft - 1, target);
if (nums[findLeft] == target) left = findLeft;
else left = findLeft + 1;
// 向右搜索目标值
int findRight = find;
while (findRight != nums.length - 1 && nums[findRight] == target) {
int findRightRes = binarySearch(nums, findRight + 1, nums.length - 1, target);
if (findRightRes < findRight + 1) break;
findRight = findRightRes;
}
if (nums[findRight] == target) right = findRight;
else right = findRight - 1;
return new int[]{left, right};
}
// find the index of target.
private int binarySearch(int[] nums, int lo, int hi, int target) {
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
int cmp = nums[mid] - target;
if (cmp > 0) hi = mid - 1;
else if (cmp < 0) lo = mid + 1;
else return mid;
}
return lo - 1;
}
有没有更好的办法呢?使用一个trick: 为了查找右边界,可以查找比目标值大一点点的值;为了查找左边界,可以查找比目标值小一点点的值。
public int[] searchRange(int[] nums, int target) {
double left = target - 0.5, right = target + 0.5;
int l = binarySearch(nums, left);
int r = binarySearch(nums, right);
if(l == r) return new int[]{-1, -1};
return new int[]{l, r - 1};
}
public int binarySearch(int[] nums, double target) {
int lo = 0, hi = nums.length - 1;
while(lo <= hi){
int mid = lo + (hi - lo)/2;
if(target > nums[mid]) lo = mid + 1;
else hi = mid - 1;
}
return lo;
}