引言:
在之前学习的二分查找中,我们全部是按照没有重复元素的顺序查找二分
那在处理有重复元素的时候我们又该如何进行查找呢
我们回到之前写过的二分查找基础版
public static int binarysearchbasic2(int[] a,int target){
int i=0,j=a.length-1;//设置初始指针
while (i<=j){
int m=(i+j)>>>1;
if (a[m]<target){
i=m+1;
}else if(target<a[m]){
j=m-1;
}else {
return m;
}
}
return -1;
}
我们不难发现,当该函数查找到第一个待查找元素的时候就会返回该元素,如果在查找一个含有重复元素的数组,其返回的也是第一个查找到的元素所在的索引。
我们能不能改进一下需求呢 比如返回重复元素中最左侧的元素
查找重复元素中最左侧的二分查找(leftmost)
我们先来看思路(蓝色为数组,紫色为数组索引值)
我们选择的目标值为4
在上述数组中,我们进行一次二分查找,成功标记到了4,我们将其进行标记,并且将查找范围的末尾指针移到标记点前一位
并完成下一次二分,同时因为此时标记的m小于4,我们将左指针移到m的右边
并找到其对应的m
此时查找到的m为更靠近左边的待查找值的索引,所以我们重新标记,并将j移到m-1,查找是否存在更近的元素
查找到j<i的时候,退出循环,标记的m即为最靠左侧的索引。
代码实现
我们只需要在对应的二分查找基础版的基础上去处理“a[m]=target”的情况即可
public static int binarysearchleftmmost1(int[]a,int target){
int i=0,j=a.length-1;
int candidate=-1;
while (i<=j){
int m=(i+j)>>>1;
if(target<a[m]){
j=m-1;
}else if (a[m]<target){
i=m+1;
}else {
candidate=m;//标记该位置
j=m-1;///查找到了待查找的元素,将末尾指针提前去查找左侧是否含有
}
}
return candidate;
}
查找重复元素中最右侧的二分查找(rightmost)
同理,我们可以得出查找重复元素最右侧的二分查找(和Leftmost不停=同的是查找到元素之后改为向右查找)
public static int binarysearchrightmmost1(int[]a,int target){
int i=0,j=a.length-1;
int candidate=-1;
while (i<=j){
int m=(i+j)>>>1;
if(target<a[m]){
j=m-1;
}else if (a[m]<target){
i=m+1;
}else {
candidate=m;//标记该位置
i=m+1;///查找到了待查找的元素,将初始指针后移去查找右侧是否含有
}
}
return candidate;
}
我们对于上述代码更进一步思考
在上述两个方法中,我们没有查找到的返回值都是-1,我们接着改进代码去使其返回值更加有用
我们将上述方法的返回值改成i,此时之前代码中的candidate没用了,可以进行删除,我们进而观察优化过的代码
以下是改进过的leftmost代码
public static int binarysearchleftmmost2(int[]a,int target){
int i=0,j=a.length-1;
int candidate=-1;
while (i<=j){
int m=(i+j)>>>1;
if(target<=a[m]){
j=m-1;
}else{
i=m+1;
}
}
return i;
}
我们对于该代码进行运行
查找值为4时,返回值为4
当查找到该元素的时候,返回的是数组中重复元素的最左边元素
查找值为5时,返回值为7
当查找不到该元素时,返回的是数组中比目标大的最靠左的值
接下来我们做一个总结
二分查找Leftmost
public static int binarysearchleftmmost2(int[]a,int target){
int i=0,j=a.length-1;
int candidate=-1;
while (i<=j){
int m=(i+j)>>>1;
if(target<=a[m]){
j=m-1;
}else{
i=m+1;
}
}
return i;
}
返回值为>=target的最左侧索引
二分查找rightmost(同理)
public static int binarysearchrightmmost2(int[]a,int target){
int i=0,j=a.length-1;
while (i<=j){
int m=(i+j)>>>1;
if(target<a[m]){
j=m-1;
}else{
i=m+1;
}
}
return i-1;
}
返回值为<=target的最靠右索引
问题1:
在rightmost中为什么返回值要是i-1,我们看下图 target值为5
下列的值里面j或者i-1代表的是<=target的最靠右索引
那在我们学习完Leftmost,Rightmost之后这些有什么用呢
我们接着讲
我们先引入这个概念
前任 后任不难理解,即为target的前一个后一个
最近邻居的意思是前后任里面数值差距最小的
在求排名的时候,可以通过Leftmost找到
比如我们求4的排名,我们得到返回值2(索引) 加上一即为4的排名
比如我们求5的排名 我们得到返回值5 索引值+1即为排名
求前任的索引
求后任的索引
接下来我们直接上所有范围
以上学习笔记参考黑马程序员,大家有兴趣深入学习可以去学习他们的教材,谢谢大家