每天一道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-----在给定序列中找到满足nums[i]>nums[i-1]&&nums[i]>nums[i+1]的位置,要求时间复杂度是O(logN)
- 每天一道LeetCode-----找到一个字符串在另一个字符串出现的位置,字符串内部顺序无要求
- 每天一道LeetCode-----寻找地增序列中第一个大于等于目标元素的位置
- 每天一道LeetCode-----给定字符串s和字符数组words,在s中找到words出现的位置,words内部字符串顺序无要求
- 每天一道LeetCode-----找到序列中第一个没有出现的正整数,要求时间复杂度是O(n),空间复杂度是O(1)
- 每天一道LeetCode-----一个整数序列,每个元素出现两次,只有一个(两个)出现一次,找到这个(这两个)元素
- 剑指offer——面试题38:数字在排序数组中出现的次数(利用二分查找来找第一次和最后一次的位置)
- Java查找指定字符串第一次或最后一次出现的位置
- 每天一道LeetCode-----找到1,2,...,n这n个数所有的组合,每个组合有k个元素,且元素大小递增
- 二分 第一次出现的位置,最后一次出现的位置
- 每天一道LeetCode-----对序列进行排序,要求nums[0] < nums[1] > nums[2] < nums[3] ....
- 每天一道LeetCode-----计算字符串s中有多少个子序列和字符串t相等
- 每天一道LeetCode-----找出给定序列的所有子序列
- Java 查找某个数字在数组中第一次和最后一次出现的位置
- 每天一道LeetCode-----给定序列中2/3/4个元素的和为target的所有集合,或3个元素的和最接近target的集合
- 统计一个字符串中某个字符第一次出现的位置和最后一次出现的位置
- 每天一道LeetCode-----摩尔投票法寻找给定数组中出现个数大于n/2或n/3的元素
- 如何找到二分查找中目标元素第一次出现和最后一次出现的位置
- 每天一道LeetCode-----计算两个序列最长的公共子序列长度
- 每天一道LeetCode-----找到给定序列中所有和为某个值的集合或集合个数,序列中可以有/无重复项,集合元素顺序不同算不同集合等