力扣题目链接
要使用二分查找的前提是要保证 有序数组,且题目假设数组内元素不重复
二分法的思想就是不断的将剩余区间对半划分,在目标值所在的那个区间继续查找
关键要注意的是边界的处理,while(left<=right)还是while(left<right)
这决定于我们选择区间的方式一种是 左闭右闭 一种是左闭右开。
以下分析都基于区间的划分方式选择 左闭右闭 时 即[left,right]此时left=right是有意义的 所以需要 <=
同时当划分区间时就需要 right=mid-1 或者 left= mid+1,因为mid已经在上一个大区间内进行过判断了,重新划分的两个小区间不需要 nums[mid] 这个元素了
public int search(int[] nums, int target) {
int left=0,right=nums.length-1,mid=(left+right)/2;
while(left<=right){//target!=nums[mid]
if(target<nums[mid]){
right=mid-1;
mid=(left+right)/2;
}
else if(target>nums[mid]){
left=mid+1;
mid=(left+right)/2;
}
else return mid;
}
return -1;
}
跳出循环的情况分析:跳出循环的条件是 left > right
1、当target不在数组中 且大于数组中的所有元素时
此时left会不断向右,直到变成 nums.length+1,此时left=nums.length+1;right = nums.length-1
2、当target不在数组中 且小于数组中的所有元素时
此时right会不断向左,直到变成 -1,此时left=0;right = -1;
3、当target不在数组中 但位于数组的范围中时
left 会在第一个大于target 的位置
right 会在第一个小于target的位置
——————————————————————————————————————————
二分查找的两个相关题目
首先改变一下mid的计算方法 这种方法计算可以避免整数溢出
可以理解为 起始位置加上区间长度的一半 ,这种方法可以在其他地方也替代(right+left)/2的写法
int mid = (right - left) / 2 + left;
然后对于插入位置,理解了上述所说的二分法跳出循环后 left 和 right 的所处位置,会发现只要插在 left 的位置就正好
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
我刚开始的做法,最不好的一点是我使用的二分法 在找到第一个目标值之后就退出二分法 之后在该位置的两边进行线性搜索 从而寻找目标元素的起始位置和结束位置 此时在最坏的情况下 就会造成时间复杂度为 O(n) 从而不符合题目要求
class Solution {
public int[] searchRange(int[] nums, int target) {
int right =nums.length-1;
int left =0;
int middle = (right+left)/2;
int start=-1,end=-1;
int[] result={-1,-1};
result[0]=start;
result[1]=end;
if (nums.length==0){
return result;
}
while(left<=right){
middle = (right+left)/2;
if(target<nums[middle]){
right=middle-1;
}
else if(target>nums[middle]){
left=middle+1;
}
else {
end = middle;
start = middle;
int i=end+1,j=start-1;
while (i<nums.length&&target == nums[i]) {
i++;
end++;
}
while (j>=0&&target == nums[j]) {
j--;
start--;
}
break;
}
}
result[0]=start;
result[1]=end;
return result;
}
}
官方题解如下
其中find函数是先找到 第一个大于等于 目标值的 元素的位置和刚才分析的情况一样
此时再进行判断是 上述说的三种情况中的哪一种
第二个max 则是找一个大于目标值的数 所在的第一个位置
public int[] searchRange(int[] nums, int target) {
int min=find(nums,target);
if(min==nums.length||nums[min]!=target){
return new int[]{-1,-1};
}
int max=find(nums,target+1)-1;
return new int[]{min,max};
}
public int find(int[] nums, int target){
int l=0;
int r=nums.length;
while(l<r){
int mid=l+(r-l)/2;
if(nums[mid]>=target){
r=mid;
}else{
l=mid+1;
}
}
return l;
}