vlambda博客
学习文章列表

详解二分查找算法(binary search)

我们小时候都玩过一个猜数字的游戏,从0-100里随机写一个数,让其他小伙伴猜是多少,别人说出一个数,只能回答大了或者小了,然后继续猜,直到猜对。
如何用最少次数更快地猜对数字?其实就是用到了二分查找的方法。假设给出[0-100]的区间范围,我们不知道数字是多少,我们可以这样猜:
1、我们第一次选择50这个数,如果大了,我们再从区间[0-49]中选择一个数24,如果小了我们则从[51-100]选择75这个数
2、第二步假设我们选择24大了,则在[0-23]区间选择一个数11。假设我们选择75大了,则在[51-74]之间选择一个数62。
......
......
以此类推,我们最多只需要七次就可以猜对。


二分查找(binary search)是一种高效的快速查找算法,对于要查找的序列是有序序列。


二分查找虽然是一种简单常用的算法思路,但是我们在用的时候经常会忽视细节。

首先我们看一个最基础的题:

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,我们需要在数组中找到这个目标值并返回它的下标,如果没有找到则返回-1。


看到这个题,我们先考虑一下常用的思路,第一种遍历整个数组nums,判断每个元素是否等于target,如果是则返回对应下标,结束返回-1。

第二种可以添加到hash表,值作为key,下标作为value。然后直接取出target值。

第一种时间复杂度O(n),空间复杂度O(1)。第二种时间复杂度O(1),但是空间复杂度O(n)。有没有更好的办法?第三种我们就可以考虑二分查找了。


我们首先思考几个问题?

1、我们查找的范围是什么?

2、用哪个值和目标值比较?

3、什么时候查找结束?


以开头的小游戏为例,第一次查找的范围为[0-100],我们取了中间值50,如果50等于目标值则结束。第二次我们有两个选择,在[0-49]之间选择或者在[51-100]之间选择,,,以此类推,我们需要选取左右的边界left和right以及中间值mid。右边界的确定直接决定了后续的结束条件和区间的选择。

主要分为两种情况:

1)右边界闭区间

如果右边界是闭区间,查找结束就是left>right的时候,也就是left=right+1的时候,再次框选区间的时候右区间也应该是闭区间

2)右边界开区间,查找结束就是left=right的时候,再次选区间的时候右区间也应该是开区间


闭区间例子如下:

python版

class Solution(object): def search(self, nums, target): """ :type nums: List[int] :type target: int :rtype: int """        low = 0        high = len(nums)-1 #右区间闭区间,搜索可以到这个边界,搜索范围[left, right]         while low<=high:#右区间闭区间,判断条件left会等于right mid = (high-low)//2+low num = nums[mid] if num==target: return mid elif num>target:                high = mid-1#右区间闭区间,搜索范围为[left,mid-1] else:                low = mid+1#搜索范围为[mid+1,right] return -1

Java版

class Solution { public int search(int[] nums, int target) { int low = 0; int high = nums.length-1;  while (low<=high){ int mid = (high-low)/2+low; int num = nums[mid]; if (num==target){ return mid; }else if (num>target){ high = mid-1; }else{ low = mid + 1; } } return -1; 
}}


开区间例子如下:

class Solution(object): def search(self, nums, target): """ :type nums: List[int] :type target: int :rtype: int """ low = 0        high = len(nums) #右区间开区间,搜索不到right,搜索范围[left, right)         while low<high:#右区间开区间,判断条件left不会等于right mid = (high-low)//2+low num = nums[mid] if num==target: return mid elif num>target:                high = mid#搜索范围在左,搜索范围为[left,mid) else:                low = mid+1#搜索范围在右区间,范围为[mid+1,right) return -1       







以上是最基础的二分查找的例子,具体的情况需要结合实际的需求灵活更改。

最后总结一下二分查找的基本框架。


int binarySearch(int[] nums, int target) {    int left = 0, right = ...;//定义左右b边界    while(...) {//判断搜索结束条件        int mid = (right + left) / 2;//计算中间值        if (nums[mid] == target) {//相等 ...        } else if (nums[mid] < target) {//在右边界的情况 left = ...        } else if (nums[mid] > target) {//在左边界的情况 right = ... } } return ...;}