您的位置:首页 > 其它

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

2017-11-03 12:05 363 查看

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;
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode
相关文章推荐