【基础算法】二分查找的多种写法
前言
在算法竞赛中,二分查找使用的频率是非常高的,对于C++选手而言,有STL中自带的lower_bound和upper_bound二分查找,可以很方便的进行二分查找。但是非C++选手、或者需要自定义多条件查找的情况需要自己写一个二分,本文对几种常见的二分查找写法进行讲解。
本文使用的编程语言为Java,但代码比较好理解其他语言选手也可以查看本文来学习二分相关内容。
二分查找
二分查找,也称折半查找,是一种可以在有序序列上进行快速查找的算法,时间复杂度是O(logn)
。
常见的二分查找类型,有以下两种:
-
查找目标值在序列中不小于目标值的第一个元素(同lower_bound,以此查到第一个位置)。
-
查找目标值在序列中第一个大于目标值的元素(同upper_bound,以此查找到最后一个出现的位置)。
在设置区间的时候,根据写法的不同分为下面几种:
-
左闭右闭。
-
左闭右开。
根据最终获取答案的写法,又分为下面两种:
-
在左右指针处取值。
-
设置额外变量取值。
本文将分不同情况,写出不同情况下的二分查找模板,帮助大家理解学习。
查找目标值在序列中不小于目标值的第一个元素
也就是实现一个STL中的lower_bound。
左闭右闭版
/*** [l,r]左闭右闭区间 lower_bound* @param nums 递增序列* @param target 目标值* @return 返回不小于目标值的第一个元素下标*/
int lowBs(int[] nums,int target){int l=0;int r=nums.length-1;while(l<=r){int mid=l+(r-l>>1);if(nums[mid]<target){l=mid+1;}else{r=mid-1;}}return l;
}
左闭右开版
/*** [l,r)左闭右闭区间 lower_bound* @param nums 递增序列* @param target 目标值* @return 返回不小于目标值的第一个元素下标*/
int lowBs(int[] nums,int target){int l=0;int r=nums.length;while(l<r){int mid=l+(r-l>>1);if(nums[mid]<target){l=mid+1;}else{r=mid;}}return l;
}
查找目标值在序列中第一个大于目标值的元素
也就是实现一个STL中的upper_bound。
左闭右闭版
/*** [l,r]左闭右闭 upper_bound* @param arr 递增序列* @param target 目标值* @return 返回第一个大于目标值的位置*/
int highBs(int[] arr,int target){int l=0;int r=arr.length - 1;while(l<=r){int mid=l+(r-l>>1);if(arr[mid]<=target){l=mid+1;}else{r=mid-1;}}return l;
}
左闭右开版
/*** [l,r)左闭右开 upper_bound* @param arr 递增序列* @param target 目标值* @return 返回第一个大于目标值的位置*/
int highBs(int[] arr,int target){int l=0;int r=arr.length;while(l<r){int mid=l+(r-l>>1);if(arr[mid]<=target){l=mid+1;}else{r=mid;}}return l;
}
易错问题和常见问题总结
一、需要注意区间定义和终止条件是否匹配正确
-
左闭右闭:初始化
r = nums.length-1
,终止条件while (l <= r)
。 -
左闭右开:初始化
r = nums.length
,终止条件while (l < r)
。
二、指针更新是否正确
-
左闭右闭:
r = mid - 1
或l = mid + 1
。 -
左闭右开:
r = mid
。
三、运算符优先级
应写为:int mid = l + ( r - l >> 1)
?为什么不能写为 l + r >> 1
如果可以保证l+r
不会溢出的话,其实这样写也可以,但是为了保证不出现数据溢出,更加推荐写上面的形式。