关于二分查找的一些思考
Posted 技术微分享
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于二分查找的一些思考相关的知识,希望对你有一定的参考价值。
最近在做二分查找的算法,发现二分查找并不是想象的那么简单,尤其是终止条件,以及mid的取值问题,总是让我晕头转向,下面以一个例子,说一下我对二分查找的理解归纳:
leetcode:剑指 Offer 53 - I. 在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
限制:
0 <= 数组长度 <= 50000
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
相信大家都已经理解了这个题目的意思:下面我说一下我当时做这个题目的思路
思路一
通过二分查找,找到target的索引index,然后从index左右扩展统计个数,代码如下:
public int search(int[] nums, int target) {
int index = getIndex(nums,target);
if(index < 0){
return 0;
}
int count = 1;
// 左侧可能还有
for(int i = index-1 ; i >= 0; i--){
if(nums[i] != target){
break;
}
count++;
}
// 右侧可能也还有
for(int i = index+1; i < nums.length; i++){
if(nums[i] != target){
break;
}
count++;
}
return count;
}
public int getIndex(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = (right - left) / 2 + left;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid - 1;
}else {
left = mid + 1;
}
}
return -1;
}
执行结果:
执行结果:通过
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:41.5 MB, 在所有 Java 提交中击败了27.56%的用户
当然这里我们不讨论执行效率,内存消耗情况,我们只是为了归纳出二分查找的一些常常让我们模糊的地方。这里我们主要讨论getIndex方法:这个方法可以归纳为一句话:从数组中找目标,找到返回index,找不到返回-1
数组寻值
问题(1):什么时候代码终止?
我们定义left=0,right=length-1,所以我们的查找范围是[left, right],闭区间,如果我们设置为 left < right 那么终止条件就是 left == right, 此时 [right, right], 恰好漏掉了一个 int[right]或者 int[left],如果我们设置为 left <= right, 那么终止条件就是 left == right+1,此时 [right+1, right] 范围为空
问题(2)int mid = (right - left) / 2 + left,为什么不直接 mid = (right+left)/2 ?
主要是考虑整型溢出的问题,这里我们不重点讨论(也没有讨论的必要,哈哈)。
问题(3)right为什么是mid - 1, left 为什么是 mid + 1 ?
主要是因为我们的范围是闭区间,而且我们已经知道了mid不符合条件;
思路二
找到左右index的位置,然后count = rightIndex - leftIndex - 1; 比如 ums = [5,7,7,8,8,10], target = 8, 4-3+1= 2
public int search(int[] nums, int target) {
if(nums.length == 0){
return 0;
}
int left = 0;
int right = nums.length;
// 寻找左侧边界
while(left < right){
int mid = left + (right-left)/2;
if(nums[mid] == target){
right = mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid;
}
}
int leftindex = left;
left = 0;
right = nums.length;
// 寻找右侧边界
while(left < right){
int mid = left + (right-left)/2;
if(nums[mid] == target){
left = mid + 1;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid;
}
}
int rightindex = right - 1;
int count = rightindex - leftindex + 1;
return count;
}
执行结果:
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:41.1 MB, 在所有 Java 提交中击败了90.76%的用户
寻找左侧边界:
问题一:为什么这里的结束条件是 left < right
这里主要是因为 left=0, right=length, [left,right) 是一个左闭右开的区间,left == right是结束的条件,如果是 left <= right,那么结束的条件是 left == right+1, left可以取到right, right+1 造成数组下标越界, 这也是为什么right=mid 和 left=mid+1的原因
问题二:为什么最后左边界是left
因为最后退出是left==right,这是结束的条件,此时nums[mid]=target,所以最后返回left和right是一样的
寻找右侧边界
关于退出条件是同样的一个道理,最后为什么返回的是right-1,是因为最后退出的条件是left == right,而此时nums[mid] == target,而left == mid + 1,所以mid == left - 1也就是right -1;
总结
在写二分查找的时候,我们应该先理解区间的开闭,然后根据开闭确定退出条件,当然无论是左开右闭还是左闭右开,我们都可以替换为双闭区间,最后的返回数据要和mid的关系去对应起来,这样面对二分查找,我们再也不会为退出条件和返回参数而惆怅了。
以上是关于关于二分查找的一些思考的主要内容,如果未能解决你的问题,请参考以下文章