LeetCode34 寻找边界
- 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
二分法寻找左右边界
叉个腰截个图😄

我只能说,细节魔鬼,细节魔鬼,细节魔鬼,,,,
1 数组严格递增
对于普通二分法,把==单独列出来,避免了进入死循环
int mid = left + ((right-left)>>1);
while(left<right){
if(nums[mid]<target){
left = mid +1;
}
else if(nums[mid]>target){
right = mid -1;
}
else{
return mid;
}
}
二分法细节魔鬼 即便避免了死循环仍然有以下几个魔鬼(死循环的事情一会再说):
-
target 不在数组里,需要返回 -1
-
target 比数组最大的数还大,容易数组越界,弱再使用 nums[left] 会报错
同理 target比数组最小的还小,再使用nums[right]会越界。
2 数组中有相等元素下用二分法
因为要寻找两侧边界,当判断了 nums[mid]==target 后,并不能直接return。而是需要把它当做一个边界继续二分
如果是寻找右侧边界,说明mid左侧一定不是右边界,需要left = mid,继续
如果是寻找左侧边界,说明mid右侧一定不是左边界,需要right = mid,继续
那么代码大致会是这样:
int mid = left + ((right-left)>>1);
while(left<right){
if(nums[mid]<target){
left = mid +1;
}
else if(nums[mid]>target){
right = mid -1;
}
else{
left = mid;
}
}
//寻找右侧边界
int mid = left + ((right-left)>>1);
while(left<right){
if(nums[mid]<target){
left = mid +1;
}
else if(nums[mid]>target){
right = mid -1;
}
else{
right = mid;
}
}
//寻找左侧边界
但是其中有一个必定出现死循环。两个mid的求法一样,都是中位数如果是偶数就选偏左的那个。也就是说比如如下的例子:
4 5
left right
mid
mid都落在了4的位置。
那么对于寻找右边界,恰好target为4的情况:根据程序判断相等以后,left = mid,这就是死循环。
而这个算法对于寻找左边界,target恰好为4的情况下,判断相等以后是挪的right,这就不是死循环。没问题。
如何处理避免不了的死循环
归根结底,就是二分到了最后,对于判断相等的情况时 : mid和 left 或right中的一个重合了,算法反复把left或right赋给相同的值。死循环了。细分就是:
- 若判断相等部分程序执行的是对left赋值,则mid是左侧中位数会陷入死循环,mid是右中位数就不会陷入死循环。
- 若程序执行的是对right赋值,则mid是做右侧中位数会陷入死循环,mid是左侧就不会
3 4 5 6 7
left right
mid
解决办法就是 修改mid 的赋值情况使得mid这样:
3 4 5 6 7
left right
mid
当程序进入相等情况判断时:
对left进行改动的,是搜索右侧边界。因为改动left,mid就应该避免落入left位置处。那mid就应该取int mid = left + ((right-left+1)>>1);
对right进行改动的,是搜索左侧侧边界。因为改动right,mid就应该避免落入right位置处。那mid就应该取int mid = left + ((right-left)>>1);
所以综合 数组越界,死循环,和数组长度为0等细节,代码如下:
class Solution{
public int[] searchRange(int[] nums, int target) {
int first = 0;
int second =0;
if(nums.length==0) return new int[]{-1,-1};
first = leftbond(nums,target);
second = rightbond(nums,target);
return new int[]{first,second};
}
int leftbond(int[] nums, int target){
int left = 0;int right = nums.length-1;
while(left<right){
int mid = left + ((right-left)>>1);
if(nums[mid]<target){
left = mid+1;
}else if(nums[mid]>target){
right = mid-1;
}else{
right = mid;
}
}
//target比最小的数小或者比最大的数大,则会有一个边界越界,先判断之,是否出现了 左侧>右侧的情况。再一般性地判断target是不是不在数组中
if(right<left||nums[left]!=target) return -1;
return left;
}
int rightbond(int[] nums, int target){
int left = 0;int right = nums.length-1;
while(left<right){
int mid = left + ((right+1-left)>>1 );
if(nums[mid]<target){
left = mid+1;
}else if(nums[mid]>target){
right = mid-1;
}else{
left = mid;
}
}
//target比最小的数小或者比最大的数大,则会有一个边界越界,先判断之,是否出现了 左侧>右侧的情况。再一般性地判断target是不是不在数组中。
if(right<left||nums[left]!=target) return -1;
return left;
}
}

浙公网安备 33010602011771号