vlambda博客
学习文章列表

你确定你真的会二分查找算法么


引子


你确定你真的会二分查找算法么


我记得我在京东的时候,面试过大概10个候选人,当我问到算法的时候,大部分人说我只会写简单的排序算法和二分查找之类的算法。


其实,我先不说排序算法就有十多种,二分查找,你确定你真的会么?


原理很简单,细节是魔鬼。




你确定你真的会二分查找算法么

详解


你确定你真的会二分查找算法么



所谓二分查找算法是针对一个排序数组的,因为数组有顺序且有固定的索引,所以你可以运用这种折半查找的算法,它的原理有点类似我们玩的一种猜数字的游戏。


就是我心中默想一个数字,你说一个数,我告诉你是大了还是小了,那么怎么猜效率才最高呢?那就是每次取中间的数字猜,一次就排出掉剩余答案的一半,这样就算有1024个数字,你也只需要10次就可以了,因为2的10次方等于1024,这种对数阶的时间复杂度有时候比常数阶都可怕,因为这个常数可能是1000,因为次数是确定的,也会被认为是常数阶。


那么,二分查找算法要怎么写,我相信绝大多数的人都能写出来以下这段代码。



private static int bSearch1(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] < target) { left = mid + 1; continue; } if (arr[mid] > target) { right = mid - 1; continue; } return mid; } return -1; }



上面这段代码也许就是大部分眼里的二分查找算法,当然其中有很多细节要注意。left <= right啊,计算两个数字的中间值int mid = left + ((right - left) >> 1); 这样可以避免当数字过大发生超出int承受的最大值的情况啊。


在我面试的时候,确实很多面试者也是给出了这个答案。那我会接着问,那你知道你这种写法适合什么场景么?


这种写法适合排序数组的数据不重复的时候。


那么还有什么情况呢?


我先把问题抛给大家,大家可以思考一下。


* 如果数组的数据有重复,我想找第一个。

* 如果数组的数据有重复,我想找最后一个。

* 我要找数组里面第一个不小于给定数字的。

* 我要找数组里面最后一个不大于给定数字的。



来吧,看看有多少人能一次性都写出来了,请在评论区告诉我,至少京东的那几个面试者,在我规定的10分钟里,都没有写出来。



你确定你真的会二分查找算法么

有重复数据寻找第一个相等的值


你确定你真的会二分查找算法么



第一种变形,我们要找第一个的话,当我们找到了相等的时候,要判断一下,这个数字是不是在索引0 的位置,然后它前面的数字是不是小于给定的数字,二者满足其一,才是正确的答案,否则right指针要继续指向mid -  1。

   

 /** * 有重复数据寻找第一个相等的值 */ private static int bSearch2(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] > target) { right = mid - 1; continue; } if (arr[mid] < target) { left = mid + 1; continue; } if (mid == 0 || arr[mid - 1] != target) { return mid; } right = mid - 1; } return -1; }



你确定你真的会二分查找算法么

有重复数据寻找最后一个相等的值


你确定你真的会二分查找算法么



第二种变形,当我们找到相等的之后,我们要判断一下这个元素是不是在数组的最后,他后面的元素是不是大于给定元素,二者满足其一才返回,否则left节点要指向mid + 1。



/** * 有重复数据寻找最后一个相等的值 */private static int bSearch3(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] > target) { right = mid - 1; continue; } if (arr[mid] < target) { left = mid + 1; continue; } if (mid == arr.length - 1 || arr[mid + 1] != target) { return mid; } left = mid + 1; } return -1;}



你确定你真的会二分查找算法么

第一个不小于给定值的数据


你确定你真的会二分查找算法么



第三种变形,如果找到一个数值大于给定数值,要判断是不是索引为0,然后前面的数字数不是小于给定数值,满足其一,返回,否则right指针要指向mid - 1。


 private static int bSearch4(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] >= target) { if (mid == 0 || arr[mid - 1] < target) { return mid; } right = mid - 1; continue; } left = mid + 1;

} return -1; }




你确定你真的会二分查找算法么

最后一个不大于给定值的数据


你确定你真的会二分查找算法么



第四种变形,如果找到一个数字小于给定数字,那么要看这个数字是不是在数组的最后,然后后面的数字是不是大于给定数值,二者满足其一,返回,否则left指针要指向mid + 1。



 /** * 找最后一个不大于给定值的数据 */ private static int bSearch5(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] <= target) { if (mid == arr.length - 1 || arr[mid + 1] > target) { return mid; } left = mid + 1; continue; } right = mid - 1; } return -1; }




最后



这五种都自己写出来了,再和面试官说我会二分查找算法,哈哈。


我是大航子,完整的代码如下,拿走不谢。



package com.example.demo.rob;

public class BSearch {

public static void main(String[] args) { System.out.println(bSearch5(new int[]{1, 1, 1, 3, 4}, 4)); }

/** * 数据不重复 */ private static int bSearch1(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] < target) { left = mid + 1; continue; } if (arr[mid] > target) { right = mid - 1; continue; } return mid; } return -1; }

/** * 有重复数据寻找第一个相等的值 */ private static int bSearch2(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] > target) { right = mid - 1; continue; } if (arr[mid] < target) { left = mid + 1; continue; } if (mid == 0 || arr[mid - 1] != target) { return mid; } right = mid - 1; } return -1; }

/** * 有重复数据寻找最后一个相等的值 */ private static int bSearch3(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] > target) { right = mid - 1; continue; } if (arr[mid] < target) { left = mid + 1; continue; } if (mid == arr.length - 1 || arr[mid + 1] != target) { return mid; } left = mid + 1; } return -1; }

/** * 找第一个不小于给定值的数据 */ private static int bSearch4(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] >= target) { if (mid == 0 || arr[mid - 1] < target) { return mid; } right = mid - 1; continue; } left = mid + 1;

} return -1; }

/** * 找最后一个不大于给定值的数据 */ private static int bSearch5(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (arr[mid] <= target) { if (mid == arr.length - 1 || arr[mid + 1] > target) { return mid; } left = mid + 1; continue; } right = mid - 1; } return -1; }}