算法018:在排序数组中查找元素的第一个和最后一个位置

在排序数组中查找元素的第一个和最后一个位置. - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/

这道题用二分查找算法。

二分查找作为所有算法中,细节最多的算法之一,原因就在本题体现:

上一个题目中,我们用到的二分查找算法只是最基础的、最朴素的通用模板,而这个题需要用到寻找左边界和右边界的方法,难度增大很多,细节也很丰富,一不留神就会出错。

我们接下来就来看这个题目。

题目的要求是返回数组中,target的起始位置和结束为止,因为本题数组是非递减的,所以一共有三种情况:

  1. 连续target在数组中,例如 5 7 7 8 8 8 8 10 ,target = 8,那么需要我们返回的结果就是[4,8]
  2. 不连续的target在数组中,只需要返回两个相同的数字就完成,例如[4,4]
  3. 数组中不存在target或者数组为空,都返回[-1,-1]

这道题的大致思路为:用二分查找找到左边界,再用二分查找找到右边界,把两个边界拼起来就是需要的结果。也就是说,这个题需要用两次二分查找,两次二分查找的很多细节都是不一样的。

查找区间的左端点:

在这个“变形的”二分查找中,我们需要找的是左端点,也就是第一个3的地方。我们直接开始二分查找,x的值就是mid的值,我们的目标是t,于是我们就拿x的值和t作比较:

  • x < t   此时让left = mid + 1 ,之后二分查找的范围就变成了[mid + 1,right]

  • x >= t  此时让right = mid 即可,如果让right = mid - 1,会出现当t = x时,mid - 1直接到左边的区间去了。操作过后,二分查找的范围就变成了[left , mid]

此时,有两个细节问题:循环条件是什么?求中点的操作怎么求?

循环条件:

循环条件是 left <= right  ,还是 left < right ?

  1. 当left = right 的时候,已经是最终结果了,无需再进行判断了
  2. 如果此时判断,就会陷入死循环

具体来说,可以分为三种情况:

判断的结果,我们判断的x值,是跟target相比较的,那么所有的情况就是这三种,对应了right和left的三种情况。当left = right的时候,就是最终结果。并且当第一种情况,再继续进行判断的时候,就会一直循环下去,进入 right = mid这个操作,会出现死循环。所以我们循环判断的条件就是:left < right 

求中点的操作:

求中点有两种操作,这么写可以防溢出。两者的差别在于,在数组是偶数个的时候,是中间靠前的数字为中点还是中间靠后的数字为中点。

只能选择第一种,让中间靠前的数为中点。

当数组只有两个数的时候,前者和后者的中点是不同的。

如果是mid在前的情况,不管是 x < t ,left = mid + 1 还是  x >= t ,right = mid都会让left = right,此时终止循环。

如果是mid在后的情况, x < t ,left = mid + 1操作是没有问题的,但是如果是x >= t ,right = mid操作,会让right一直处于right = mid操作的死循环中。

所以求中点的操作,只能是 left + (right - left) / 2

此时,查找区间左端点的操作已经全部完成了。我们进入查找区间右端点的操作。

有了前面查找左端点的操作,查找右端点的操作就变得很简单了:

  1. 当x <= t 时,left = mid
  2. 当x > t 时,right = mid - 1

类似于把前面的操作对称一下。

循环条件:

仍然是left < right。

求中点的方式:

此时就变了一种方式,仍然是用前面的思想,此时变成 left + (right - left + 1) / 2  不会出现死循环,是符合题意的条件。

总结一下:二分查找左右边界的模板:

  查找区间左端点的模板:
    while(left < right){
        int mid = left + (right - left) / 2;
        if(...){
               left = mid + 1;
        }else{
               right = mid;
        }
    }
  查找区间右端点的模板:
    while(left < right){
        int mid = left + (right - left + 1) / 2;
        if(...){
               left = mid;
        }else{
               right = mid - 1;
        }
    }

通过两次二分查找,并且需要完善一下特殊的情况,我们的代码:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ret = new int[2];
        ret[0] = ret[1] = -1;
        if(nums.length == 0){
            return ret;
        }
        
        int left = 0,right = nums.length - 1;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[mid] < target){
                left = mid + 1;
            }else{
                right = mid;
            }
        }
        if(nums[left] != target){
            return ret;
        }else{
            ret[0] = right;
        }

        left = 0;
        right = nums.length - 1;
        while(left < right){
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target){
                left = mid;
            }else{
                right = mid - 1;
            }
        }
        ret[1] = left;
        return ret;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值