LeetCode34 寻找边界

  1. 在排序数组中查找元素的第一个和最后一个位置
    给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

二分法寻找左右边界

叉个腰截个图😄

image-20211210170948127

我只能说,细节魔鬼,细节魔鬼,细节魔鬼,,,,

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;
		}
}

二分法细节魔鬼 即便避免了死循环仍然有以下几个魔鬼(死循环的事情一会再说):

  1. target 不在数组里,需要返回 -1

  2. 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;

    }

}
posted @ 2021-12-10 17:07  淮南枳  阅读(81)  评论(0)    收藏  举报