每天一道LeetCode-----某个数在递增序列第一次和最后一次出现的位置

来源:互联网 发布:推广平台源码 编辑:程序博客网 时间:2024/05/19 11:48

Search for a Range

原题链接Search for a Range
这里写图片描述
给定一个递增序列和一个值,找到该值在序列中出现的范围,实际上就是找到该值第一次出现和最后一次出现的位置。如果没有,返回[-1,-1]


递增序列肯定是二分了,正常二分法查找算法如下,是通过判断中间位置的值与给定值的大小关系,从而将区间变为原来的一半,继续查找,不断的一半,一半,最后变成只有一个元素的区间,比较后返回。

int binary_find(vector<int>& nums, int target){    int left = 0;    int right = nums.size();    while(left <= right)    {        int middle = (left + right) / 2;        if(nums[middle] == target)            return middle;        else if(nums[middle] > target)            right = middle - 1;        else            left = middle + 1;    }    return -1;}

普通的二分法查找到一个相等的值就结束了,但是这里需要确定这个值第一次出现和最后一次出现的位置。所以很明显不能让它结束这么快,也就是说即使nums[middle] == target,也不返回,因为目的是要找一个范围,即两个边界,而middle只是一个点,不一定是边界,有可能middle前面和后面也都是等于target的位置。
但是又因为二分法最后肯定会收敛到一个点,不能直接找范围,所以可以先找左边界,再找右边界


二分法需要保证,如果序列中存在目标元素target,那么最后收敛到的位置的值一定是target

对于左边界
考虑二分法的实现,每次找到middle后比较nums[middle]和target的大小关系

  • 如果nums[middle] > target,说明target在[left, middle)区间,改变right = middle - 1;
  • 如果nums[middle] < target,说明target在(middle, right]区间,改变left = middle + 1;
  • 如果nums[middle] == target,说明第一次出现target的位置在[left, middle]内,改变right = middle。因为middle位置有可能就是第一次出现target的位置,所以不能让right = middle - 1;

对于右边界
考虑二分法的实现,每次找到middle后比较nums[middle]和target的大小关系

  • 如果nums[middle] > target,说明target在[left, middle)区间,改变right = middle - 1;
  • 如果nums[middle] < target,说明target在(middle, right]区间,改变left = middle + 1;
  • 如果nums[middle] == target,说明最后一次出现target的位置在[middle, right]内,改变left = middle。因为middle位置有可能就是最后一次出现target的位置,所以不能让right = middle - 1;

但是!考虑一种情况,求右边界时,某次区间[left, right]长度只有2,也就是说right = left + 1,这就导致middle = left。如果nums[middle] == target,根据上面的式子,另left = middle,此时left根本没有变化,也就是说改变left后区间根本没有更新,会陷入无限循环

这种问题只出现在求右边界的情况,原因是在求左边界时,right不可能和middle相等,所以每次区间都会变小,不会出现上面的问题

怎么解决呢,可以当区间长度为2时手动判断nums[left]和nums[right]。因为目的是求最后一个target出现的位置,而right的位置是区间的最右边,所以如果nums[right] == target,那么根本就不需要再找了,right就是最后一次出现target的位置 ,而如果nums[right] != target,那么right -= 1将right左移。

代码如下

class Solution {public:    vector<int> searchRange(vector<int>& nums, int target) {        if(nums.empty())            return {-1, -1};        int front = equalLeftBound(nums, target);        int back = equalRightBound(nums, target);        if(front < nums.size() && back >= 0 && nums[front] == target && nums[back] == target)            return {front, back};        else            return {-1, -1};    }private:    /* 寻找第一次出现target的位置 */    int equalLeftBound(vector<int>& nums, int target)    {        int left = 0;        int right = nums.size() - 1;        while(left < right)        {            int middle = (left + right) / 2;            /*             * if(nums[middle] < target)             *     left = middle + 1;             * else if(nums[middle] > target)             *     right = middle - 1;             * else             *     right = middle;             */            /*              * 这里将nums[middle] > target和nums[middle] == target合在一起             * 对于left是否需要加一可以通过nums[middle]是否等于target判断             * 因为right永远不会和middle相等,所以区间会一直减小,不会出现无限循环             * 怎么写无所无,但是右边界不行             */            if(nums[middle] < target)                left = middle + 1;            else                right = middle;            /*              * 如果nums[middle] < target导致left = middle + 1后             * nums[left]仍然小于target会导致left继续加一,这里可能出现left > right的情况             * 如果此时right = nums.size() - 1,那么left就越界了             * 返回的left也就越界了,需要在返回后判断             */            if(nums[left] == target)                break;            else                ++left;        }        return left;    }    /* 寻找最后一次出现target的位置 */    int equalRightBound(vector<int>& nums, int target)    {        int left = 0;        int right = nums.size() - 1;        while(left < right)        {            /*              * 这里只能合在一起,因为left可能和middle相等,导致区间根本没有更新,导致无限循环             * 所以需要改变区间,从而将区间缩小             */            int middle = (left + right) / 2;            if(nums[middle] > target)                right = middle - 1;            else                left = middle;            /* 如果右边界就是target,直接返回即可,否则需要将right减小,因为最后的结果是right */            /*              * 同理左边界,如果nums[middle] > target导致right = middle - 1             * 而nums[right]仍然大于target,会导致right继续减一,可能出现right < left的情况             * 如果此时left = 0,那么right就越界了,需要在返回后判断是否越界             */            if(nums[right] == target)                break;            else                --right;        }        return right;    }};
阅读全文
0 0