一.概念
二分查找法,它充分利用了元素间的次序关系,采用分治策略,可在最坏的情况下用O(log n)完成搜索任务。它的基本思想是:(这里假设数组元素呈升序排列)将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较,如果x=a[n/2]则找到x,算法终止;如 果x<a[n/2],则我们只要在数组a的左半部继续搜索x;如果x>a[n/2],则我们只要在数组a的右 半部继续搜索x。
二.在题目中掌握思想(代码中会有注释)
ps:题目均选自力扣
1.二分查找
题面:
基本分析:可以通过遍历一遍查找target,这样时间复杂度是O(n),而二分是O(logn),而在二分的基础使用中应该注重的就是边界问题,常见的是左闭右开和左闭右闭,什么时候l<=r,什么时候l<r,什么时候l=m+1等等。下面会有两版代码分别讲解左闭右开和左闭右闭
左闭右闭:
class Solution {
public int search(int[] nums, int target) {
int l = 0;//定义左边界
//定义右边界,此时我们是以nums.length-1为右边界,
//那么我们是认为右边界是有意义的,请牢记这一点
int r = nums.length-1;
int flag = -1;//用来标记目标的索引
while(l<=r){//因为右边界是有意义的,所以i可以等于j
int m = (l+r)>>>1;//相当于(i+j)/2,在java中整数间的除法是向下取整
//nums[m]>target,则索引m右边的元素都大于target,由于m不可能是target且j有意义,所以j=m-1
if(nums[m]>target)r = m-1;
//nums[m]<target,则索引m左边的元素都小于target,由于m不可能是target且i有意义,所以i=m+1
else if(nums[m]<target) l=m+1;
//相等,则是目标元素,标记并跳出循环
else {
flag = m;
break;
};
}
return flag;
}
}
左闭右开:
class Solution {
public int search(int[] nums, int target) {
int l = 0;//定义左边界
//定义右边界,此时我们是以nums.length为右边界,
//由于右边界超出了数组索引范围,那么我们是认为右边界是没有意义的,请牢记这一点
int r = nums.length;
int flag = -1;//用来标记目标的索引
while(l<r){//因为右边界是没有意义的,所以i不等于j
int m = (l+r)>>>1;//相当于(i+j)/2,在java中整数间的除法是向下取整
//nums[m]>target,则索引m右边的元素都大于target,由于m不可能是target且j没有意义,所以j=m
if(nums[m]>target)r = m;
//nums[m]<target,则索引m左边的元素都小于target,由于m不可能是target且i有意义,所以i=m+1
else if(nums[m]<target) l=m+1;
//相等,则是目标元素,标记并跳出循环
else {
flag = m;
break;
};
}
return flag;
}
}
现在你应该对二分有了一个基本的认识了
2.搜索插入位置
题目链接:35. 搜索插入位置 - 力扣(LeetCode)
题面:
基本分析:从排序数组和时间复杂度不难看出,这道题需要我们使用二分。首先,如果目标值存在于数组,那么思想就和第一题一样,如果目标不存在呢?我们想一下,假如我们采用左闭右闭区间,循环的条件是l<=r,那么循环破除的条件便是l>r,当循环来到最后一步,也就是进入循环时l=r,此时m也等于l和r,这里破除循环分两种情况:
1.r=m-1导致l>r,跳出循环
由于我们的判断条件可知,m索引右边的元素始终大于目标元素,m索引左边的元素始终小于目标元素,最后r=m-1,也就是m索引所指向的元素大于目标元素,而m-1所指向的元素肯定是小于目标元素,题目需要我们返回插入被按顺序插入的位置,也就是第一个比目标元素大的位置,也就是m,也就是l。
2.l=m+1导致l>r,跳出循环
也就是m索引所指向的元素小于目标元素,而m+1所指向的元素肯定是大于目标元素,题目需要我们返回插入被按顺序插入的位置,也就是第一个比目标元素大的位置,也就是m+1,由于l刚好被加一了,所以就是l。
综上所述,如果目标元素存在于数组内,我们返回标记值,如果不存在,返回l。
代码:
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0;
int r = nums.length-1;
int flag = -1;
while(l<=r){
int m = (l+r)>>>1;
if(nums[m]>target)r=m-1;
else if (nums[m]<target)l=m+1;
else {
flag = m;
break;
}
}
return flag==-1?l:flag;
}
}
3.在排序数组中查找元素的第一个和最后一个位置
题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
题面:
基本分析: 在左闭右闭中,如果我把判断条件改为nums[m]>=target,r=m-1亦或者nums[m]<=target,l=m+1会发生什么变化?
我们就拿nums[m]>=target,r=m-1来讲述,则循环条件中代码如下:
while(l<=r){
int m = (l+r)>>>1;
if(nums[m]>=target)r=m-1;
else l = m+1;
}
上述代码:我们假设循环来到了最后一步,也就是l=r=m,目标元素(可以是多个)存在于数组内我们分为两种情况:
a.是因为l=m+1导致循环跳出
如果走的是第一个条件,也就是nums[m]>=target,由于目标值存在于数组内,什么情况下不走第一个?也就是上一步中nums[m]==target,然后走了第一个条件了。那我们就可以推断出m+1就是目标元素最左边的所以,由于l+1了,所以就是l;
b.是因为r=m-1导致循环跳出
说明走的是第一个条件,由于是最后一步,不可能存在m索引左边存在元素大于等于target,因为此时i都等于m了,也就是说,最后一步是nums[m]==target,m就是目标元素最左边的位置,所以也就是l;
思想知道了,代码奉上:
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] arr2 = { -1, -1 };
if (nums.length == 0)
return arr2;
int i1 = 0, i2 = 0;
int j1 = nums.length - 1, j2 = nums.length - 1;
while (i1 <= j1) {
int m = (i1 + j1) >>> 1;
if (nums[m] >= target)
j1 = m - 1;
else
i1 = m + 1;
}
if (!(i1 <= j2 && nums[i1] == target))
return arr2;
arr2[0] = i1;
while (i2 <= j2) {
int m = (i2 + j2) >>> 1;
if (nums[m] <= target)
i2 = m + 1;
else
j2 = m - 1;
}
arr2[1] = j2;
return arr2;
}
}
三.后言
上面是二分查找一些最经典的题目,以后碰到其他好题也会更新,希望有所帮助,一同进步,共勉!

514

被折叠的 条评论
为什么被折叠?



