二分查找法
力扣链接:34. 在排序数组中查找元素的第一个和最后一个位置
注意事项:
- 当考虑查找元素的边界如0(数组没有元素),代码块需要判断length-1合法可以利用这个下标取出数组中的值,当数组为空时,会出现数组溢出的情况。
- 在计算数组中间值的时候,为了防止数组溢出 使用的是middle = left + ((right - left) >> 1),使用>>表示右移 比% /的效率高。
方法一
这道题先使用传统的二分算法 查找中间的middle值,有两种可能:
- 第一种是找到了,那么接下来从中间开始查找左右区间,从middle往前或者往后移动(使用两个变量可以使用之前使用的两个left和right变量)但是这里因为移动变量后续通过变量取数组中的数值,所以需要考虑变量溢出的情况,即left和right和0和length-1的关系,如果溢出则break
- 第二种没有找到 则返回[-1,-1]
代码如下:
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] result = {-1, -1};
if(nums.length==0 || target < nums[0] ||target > nums[nums.length - 1] ){
return result;
}
int left = 0;
int right = nums.length - 1;
int middle = 0;
while(left <= right){
middle = left + ((right - left) >> 1);
if(nums[middle] > target){
right = middle - 1;
}else if(nums[middle] < target){
left = middle + 1;
}else{
break;
}
}
if(left > right){
return result;
}
left = middle;
right = middle;
while(nums[left] == target){
left -= 1;
if(left < 0 ){
break;
}
}
while(nums[right] == target){
right += 1;
if(right > nums.length - 1){
break;
}
}
result[0] = left + 1;
result[1] = right - 1;
return result;
}
}
方法二
首先需要先考虑3种情况:
- 第一种,即不存在目标元素:目标元素不存在数组中(这里利用找到的边界的差等于1 进行排除)
- 第二种,即目标元素在其中(通过找到的边界差大于1 进行获取)
- 第三种 数组里面元素为空;目标元素和下标边界的直接对比小于0,大于length-1 :通过未赋值的边界进行处理[-2, -2]
根据以上三种情况,写算法,如何确定上下边界呢?通过修改二分查找中的相等条件,使用left确定上边界,即相等的时候移动left;使用right确定下边界,即相等的时候移动right。
这里考虑了特殊情况,当目标元素小于nums[0]时,当确定上边界时,需要移动left,而left移动的条件是target更大,所以不可能被赋值,同理目标元素大于nums[nums.length-1]时,确定下边界,需要更新right,而right的值更新是小于某个值,所以下边界不能被确认。
区分情况一和情况二,通过赋值之后的元素差进行区分,如果相差为1,则说明不存在目标元素,反之则存在。
因为下边界是找到目标数组中第一小于目标元素的值(小于目标元素的最大值),同理上界也是,所以可以通过下标确定元素是否存在,不存在,则小边界和上边界连续。
代码如下
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] result = {-1, -1};
// 这里先判断length==0,如果移动了顺序 那么后面会报错(数组溢出问题)
// 通过这几种判断 解决情况三
if(nums.length==0 || target < nums[0] ||target > nums[nums.length - 1] ){
return result;
}
int rightBorder = getRightBorder(nums, target);
int leftBorder = getLeftBorder(nums, target);
// 这里只需要直接判断找到了的情况
if(rightBorder - leftBorder > 1 ){
result[0] = leftBorder + 1;
result[1] = rightBorder - 1;
return result;
}
// 没有目标元素 即是第一种情况,不用再进行判断了
return result;
}
int getRightBorder(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
// 上边界 通过移动left获取,所以相等的情况是left移动
int rightBorder = -2;
int middle ;
while(left <= right){
middle = left + ((right - left) >> 1);
if(nums[middle] > target){
right = middle - 1;
}else{
left = middle + 1;
// 最终上边界是目标上边界+1 因当相等时,需要再往上移动一位
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
// 下边界 通过移动right获取,所以相等的情况是right移动
int leftBorder = -2;
int middle;
while(left <= right){
middle = left + ((right - left) >> 1);
// 相等时 是right需要移动
if(nums[middle] >= target){
right = middle - 1;
// 最终下边界是目标下边界-1 因当相等时,需要再往下移动一位
leftBorder = right;
}else{
left = middle + 1;
}
}
return leftBorder;
}
}