审题:
本题需要我们找出非递减数组中target值的起始索引和终止索引,若没有target值就返回{-1,-1}
思路:
方法一:暴力搜索我们可以利用双层for循环搜索所有子段,然后找到target的起始索引和终止索引。
不过他的时间复杂度就是O(n),不符合题目的要求
方法二:二分查找
由于本题的数组有一个不递减的特性,所以我们其实可以根据target值将数组划分为两个区域,一个区域是值小于等于target,另一个是大于target。这就是当前数组数据的二段性,根据这个二段性,我们可以使用二分算法
第一步:查找左边界
1.进行前提:left < right
因为当left等于right的时候,说明我们要么找到了正确边界,要么该值不存在。若等于的情况都继续搜会出现死循环,后面我们详细分析
2.mid的求法:
由于这里我们求的是左边界,所以我们最终是要让right去靠近left的,所以要用(left+right)/ 2来计算mid。
补充:
在right与left之差为偶数时,后面加的1不影响mid的结果
若是差为奇数,则加1会让mid偏右一位,不加会让mid偏左一位
这里我们的right和left的差为3,为奇数,所以我们加一之后得到的索引为2,不加得到的索引为1,这就是前面说的,加一会让mid偏右一位,不加会偏左一位
修正:图中应该使用索引3-0,而不是4-1,这里写错了
3.分类讨论:
当mid索引处的值大于等于target时,说明mid右边区域的值都可以从最小左边界中排除,让right指向mid索引的位置。
当mid索引处的值小于target时,说明mid左边的区域也可以排除,让left指向mid+1的位置
第二步:查找右边界
和左边界原理一致,注意点我们在代码区域讲解
解题:
(1)空数组处理
if(nums.empty())//空数组情况 return {-1,-1};
(2)查找左边界
int leftans = -1,rightans = -1; //查找左边界 int left = 0; int right = nums.size()-1; int mid = 0; while(left < right) { mid = (left+right)/2; if(nums[mid] >= target) { right = mid; } else { left = mid+1; } } if(nums[left] == target) leftans = left; else return {-1,-1};
注意:
1.当我们的left等于right后,还需要检查当前索引指向的值是否等于target值,若不等于说明数组中不存在target值,直接返回{-1,-1}
2.不让left等于right作为循环进行条件的原因:
若left等于right可以作为循环进行条件,那么我们要么会导致left>right,要么就陷入死循环(一直让right更新为mid,相当于没变),而陷入死循环是绝对不允许的
(3)查找右边界
//查找右边界 left = 0; right = nums.size()-1; mid = 0; while(left < right) { mid = (left+right+1)/2; if(nums[mid] <= target) { left = mid; } else { right = mid-1; } } rightans = right; return {leftans,rightans};
注意:
1.mid的计算需要加一:为了让left去靠近right
2.右边界不用检查值是否等于target:因为能进入到右边界搜索说明左边界一定存在,也就是target一定存在,所以右边界一定存在