今天刷LeetCode的时候,遇到了一道二分查找的题目。代码轮廓很容易就写出来了,但是,调边界条件却调了很久。原因还是因为自己对二分查找掌握的不够透彻。于是又查了一些资料,看了别人的一些博客,再次学习了二分查找~下面总结今天看的七种二分查找的类型,代码轮廓大致一样,大多是是判断条件不一样。
注:以下讨论全是在不严格递增数组下实现的,例如,array[] = {1,2,2,3,3,4,4,5,6,7}
1. 基础版——查找有序数组中key的下标
查找有序数组中key的下标,若存在多个,则返回任意一个下标,不存在该数的话则返回-1。
解析:当我们找到array[mid] == key
时,我们应该返回mid的值。当我们找到array[mid] > key
时,那么我们需要求的元素应该在array[mid]
之前,所以将end
指针赋值为mid - 1
。当我们找到array[mid] < key
时,那么我们需要求的元素应该在array[mid]
之后,所以将start
指针赋值为mid + 1
。
//非递归版
int binSearchIndex(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
while(start <= end){
mid = start + ((end - start ) >> 1);
if(array[mid] == key){
return mid;
}else if(array[mid] < key){
start = mid + 1;
}else{
end = mid - 1;
}
}
return -1;
}
//递归版
int binSearchIndexRecursion(int array[] , int start , int end , int key){
int mid = (start + end) >> 1;
if(array[mid] == key){
return mid;
}
if(array[mid] > key){
binSearchRecursion(array , start , mid - 1 , key);
}else if(array[mid] < key){
binSearchRecursion(array , mid + 1 , end , key);
}
}
2. 基础版——查找有序数组中第一个大于等于key的下标
查找有序数组中第一个大于等于key的下标,如果不存在,则返回-1。
解析:当我们找到array[mid] >= key
时,我们应该假设它不是第一个大于等于key
的元素,那么我们需要求的元素应该在array[mid]
之前,所以将end
指针赋值为mid - 1
。当我们找到array[mid] < key
时,那么我们需要求的元素应该在array[mid]
之后,所以将start
指针赋值为mid + 1
。这里我们应该判断一下最后返回的start
值有没有超过数组的长度,如果超过了,说明不存在大于等于key
的元素,应该返回-1。
//非递归版本
int binSearch(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
while(start <= end){
mid = start + ((end - start) >> 1);
if(array[mid] >= key){
end = mid -1 ;
}else {
start = mid + 1;
}
}
return start < len ? start : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
int mid = start + ((end - start) >> 1);
if(start > end){
return start < len ? start : -1;
}
if(array[mid] >= key){
binSearchRecursion(array , start , mid - 1 , len , key);
}else{
binSearchRecursion(array , mid + 1 , end , len , key);
}
}
3. 基础版——查找有序数组中第一个大于key的下标
查找有序数组中第一个大于key的下标,如果不存在,则返回-1。
解析:当我们找到array[mid] <= key
时,那么我们需要求的元素应该在array[mid]
之后,所以将start
指针赋值为mid + 1
。当我们找到array[mid] > key
时,我们应该假设它不是第一个大于key
的元素,那么我们需要求的元素应该在array[mid]
之前,所以将end
指针赋值为mid - 1
。这里我们应该判断一下最后返回的start
值有没有超过数组的长度,如果超过了,说明不存在大于key
的元素,应该返回-1。
//非递归版本
int binSearch(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
while(start <= end){
mid = start + ((end - start) >> 1);
if(array[mid] <= key){
start = mid + 1 ;
}else{
end = mid - 1;
}
}
return start < len ? start : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
int mid = (start + end) >> 1;
if(start > end){
return start < len ? start : -1;
}
if(array[mid] > key){
binSearchRecursion(array , start , mid - 1 , len , key);
}else{
binSearchRecursion(array , mid + 1 , end , len , key);
}
}
4. 扩展版——查找有序数组中key元素位置的最小下标
查找有序数组中key元素位置的最小下标,没有key元素则返回-1。
解析:其实这个拓展版本和上面第二个基础版本基本一样的道理。唯一的区别是:上面的第二个基础版我们返回的第一个大于等于key
的元素的下标值,即array[start] >= key
;而这个拓展版要求我们找一个第一个等于key
的元素下标值,即最小的start
使得array[start] = key
。所以,我们只需要在最后return
的时候取判断一下返回的下标对应的值是不是等于key
。其实,在第二个基础版中,如果数组中有key
的话,返回的必定是key
元素的最小下标,如果不存在key
的话,返回的才是大于key
的第一个元素,而前一种情况正是我们这个版本中需要求解的问题。
//非递归版本
int binSearch(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
int ans = -1;
while(start <= end){
mid = start + ((end - start) >> 1);
if(array[mid] >= key){
end = mid -1 ;
}else {
start = mid + 1;
}
}
return ((start < len) && (array[start] == key)) ? start : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
int mid = start + ((end - start) >> 1);
if(start > end){
return ((start <= len) && (array[start] == key)) ? start : -1;
}
if(array[mid] >= key){
binSearchRecursion(array , start , mid - 1 , len , key);
}else{
binSearchRecursion(array , mid + 1 , end , len , key);
}
}
5. 扩展版——查找有序数组中key元素位置的最大下标
查找有序数组中key元素位置的最大下标,没有key元素则返回-1。
解析:这个版本可以跟第三个基础版(查找有序数组中第一个大于key
的下标)联系起来。当我们找到了有序数组中第一个大于key
的下标,我们只需要在返回的时候去判断array[start - 1] == key
。既然array[start]
是第一个大于等于key
的数,那么如果array[start - 1] == key
,那么start - 1
必定是我们要求的那个下标,否则不存在。这里还有一个地方需要注意就是start - 1 < 0
, 所以,顺便判断一下start >= 1
,这里不需要判断start <= len
,因为start
最多到len
,这时候start - 1
还是符合要求的。
//非递归版本
int binSearch(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
while(start <= end){
mid = start + ((end - start) >> 1);
if(array[mid] <= key){
start = mid + 1;
}else {
end = mid -1 ;
}
}
return ((start >= 1) && (array[start - 1] == key)) ? start - 1 : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
int mid = start + ((end - start) >> 1);
if(start > end){
return ((start >= 1) && (array[start - 1] == key)) ? start - 1 : -1;
}
if(array[mid] > key){
binSearchRecursion(array , start , mid - 1 , len , key);
}else{
binSearchRecursion(array , mid + 1 , end , len , key);
}
}
6. 扩展版——查找有序数组中小于key元素的最大下标
查找有序数组中小于key元素的最大下标,没有key元素则返回-1。
解析:这个拓展版和第二个基础版(查找有序数组中第一个大于等于key的下标)可以联系起来。当我们找到了有序数组中第一个大于等于key
的下标,那么array[start - 1] < key
在start >= 1
的时候必然是成立的。所以,和第二个基础版本的返回条件不同的是,我们需要判断一下start >= 1
,这里不需要判断start <= len
,因为start
最多到len
,这时候start - 1
还是符合要求的。
//非递归版本
int binSearch(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
while(start <= end){
mid = start + ((end - start) >> 1);
if(array[mid] >= key){
end = mid -1 ;
}else {
start = mid + 1;
}
}
return (start >= 1) ? start - 1 : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
int mid = start + ((end - start) >> 1);
if(start > end){
return (start >= 1) ? start - 1 : -1;
}
if(array[mid] >= key){
binSearchRecursion(array , start , mid - 1 , len , key);
}else{
binSearchRecursion(array , mid + 1 , end , len , key);
}
}
7. super版——查找有序数组中key元素出现的个数
查找有序数组中小于key元素出现的个数。
解析:这个就是第二个基础版和第三个基础版的组合,如果数组中存在key
元素的话,第二个基础版(查找有序数组中第一个大于等于key
的下标)就是找到第一个key
元素,第三个基础版(查找有序数组中第一个大于key
的下标)就是查找的最后一个key
元素之后的一个值,两者返回值相减便是我们需要求的key
元素的个数。如果数组中不存在key
元素的话,第二个基础版(查找有序数组中第一个大于等于key
的下标)和第三个基础版(查找有序数组中第一个大于key
的下标)查找的结果是相同的,都是大于key元素的第一个值,两者返回值相减为0,表明有0个key
元素。
//非递归版本
int binSearchMaxIndex(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
while(start <= end){
mid = start + ((end - start) >> 1);
if(array[mid] <= key){
start = mid + 1 ;
}else{
end = mid - 1;
}
}
return start < len ? start : -1;
}
int binSearchMinIndex(int array[] , int len , int key){
int start = 0 , end = len - 1 , mid = 0;
while(start <= end){
mid = start + ((end - start) >> 1);
if(array[mid] >= key){
end = mid -1 ;
}else {
start = mid + 1;
}
}
return start < len ? start : -1;
}
//递归版本
int binSearchRecursionMaxIndex(int array[] , int start , int end , int len , int key){
int mid = (start + end) >> 1;
if(start > end){
return start < len ? start : -1;
}
if(array[mid] > key){
binSearchRecursionMaxIndex(array , start , mid - 1 , len , key);
}else{
binSearchRecursionMaxIndex(array , mid + 1 , end , len , key);
}
}
int binSearchRecursionMinIndex(int array[] , int start , int end , int len , int key){
int mid = start + ((end - start) >> 1);
if(start > end){
return start < len ? start : -1;
}
if(array[mid] >= key){
binSearchRecursionMinIndex(array , start , mid - 1 , len , key);
}else{
binSearchRecursionMinIndex(array , mid + 1 , end , len , key);
}
}
PS:以上代码我已经在自己的机器上测试过,如果大家发现问题,欢迎指正~笔芯(•‾̑⌣‾̑•)✧˖°