34. 在排序数组中查找元素的第一个和最后一个位置

143 阅读3分钟

[34. 在排序数组中查找元素的第一个和最后一个位置]

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

题目描述

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

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

进阶:

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

示例

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

示例 3:

输入: nums = [], target = 0
输出: [-1,-1]

思路

经过之前三道二分查找的算法题,今天使用一道典型的二分查找来对可以使用二分查找的题进行一个拓展。首先,在这道题里面,我们还是在一个在有序的数组中,找到特定的一个值。但是今天的这道题有所不同,特点的值可能不存在,并且值是会存在重复的。明确使用二分查找之后,下来就是明确区间的定义了,这里区间的定义与昨天的一致。

代码实现

我们使用左闭右闭的写法来解决这道题:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> result(2,-1);    //初始化结果为找不到这样的值
        int left=0;
        int right= nums.size()-1;    //左闭右闭区间
      
        while(left<=right){          // while 循环中找到第一个大于等于目标值的下标
            int middle=left+((right-left)>>1);
            if(nums[middle]<target) left=middle+1;
            else right=middle-1;           
        }
        //cout<<left<<" "<<right<<endl;
        if(left>= 0 && left<nums.size() && nums[left]==target){   //保证该下标下的值为目标值
            result[0]=left;
        }
        left=0;
        right=nums.size()-1;
         while(left<=right){        // while 循环中找到最后一个小于等于目标值的下标
            int middle=left+((right-left)>>1);
            if(nums[middle]>target) right=middle-1;
            else left=middle+1;           
        }
       // cout<<left<<" "<<right<<endl;
       if(right>=0 && right<nums.size() && nums[right]==target){    //保证该下标下的值为目标值
        result[1]=right;
       }
        return result;
    }
};

与之前的写法对比,可以发现以下不同:在while循环中,判断分支只有2个;返回的结果不同,有时返回的是left,有时是right。其实这些改变多少针对重复的情况,进行特定修改的。

首先,我们先来看第一个while,我们需要在这里里面查找元素的第一个位置。这里我们需要想一个问题,如果在一次循环中,num[mid]==target时,我们该怎么做,如何判断它是第一个元素。有人说,直接向左右遍历,就可以找到第一和最后一个,但是一个一个找,就很笨。话说回来,我们要找第一个出现的元素,当num[mid]==target 时,我们要的下标必定 <=mid,此时,我们应该将区间向左缩小,改变right,继续找,这与num[mid]>target 时的做法一致,所以我们可以合并分支,将num[mid]>=target 的情况写在一起,而具体到返回left,还是right呢,我们可以考虑当left==right进行最后一次循环时的情况:如果此时nums[left]==nums[right]==target,即我们有找到这样的值,此时的left和right就是我们要的结果,但此时会进入num[mid]>=target的分支,right会被修改,此时只有left是结果了,当nums[left]==nums[right]!=target时,说明数组中不存在这样的元素,此时的left会停留在第一个>=的下标,而right会是left-1,前提是left和right都没有溢出。

同理第二个while,num[mid]==target 时,我们要的下标必定 >=mid,此时,我们应该将区间向右缩小,改变left。 合并分支。当left==right进行最后一次循环时的情况:如果此时nums[left]==nums[right]==target,即我们有找到这样的值,此时的left和right就是我们要的结果,但此时会进入num[mid]<=target的分支,left会被修改,此时只有right是结果了,当nums[left]==nums[right]!=target时,说明数组中不存在这样的元素,此时的right会停留在最后一个<=的下标,而left会是right+1,前提是left和right都没有溢出。

总结

上述内容对二分查找进行了拓展,针对了数组中存在重复元素的情况。