二分法是把元素分为“存在”和“不存在”两部分,不断去掉“不存在”的部分,最终找到满足要求的元素。常见的二分法用于在排序好的元素中查找某个值,每次去除一半,效率较高。但是,不一定只在这类有序状态下才能使用二分,如果能够构建标准,判断两侧是否有需要的元素,并可以不断去除“不存在”的部分,就也可以用二分法来解决。下面通过几个实例加深对二分法的理解。
这里先补充回顾几个位运算符的知识点:
右移”>>“:带符号右移,左侧用符号来补位。如果 a>>1,表示a带符号右移一位,得到的结果相当于a/2。
无符号右移”>>>“: 不管符号,左侧一律用0来补位。
左移”<<“:左移只有这个符号,用0补位。 如果a<<1,得到的结果相当于a*2。
取两个数中间值的方法:int mid = (left + right)/2; 但这种方法left+right有可能溢出。int mid = left + (right - left)/2; 这种方法可以防止溢出,但是算术运算比位运算要慢。因此结合上面回顾的知识点,较好的写法为:
int mid = left + ((right - left)>>1);
(1) 在一个有序数组中,找某个数是否存在
假设数组中的数从小到大排列。先确定数组中元素的中间位置,如果等于要找的数,就直接返回;如果小于要找的数,就说明要找的数在中间位置的右侧,直接忽略掉左侧的内容,找右侧的中间位置,重复此过程,直至找到该元素(存在)或者排除掉所有元素(不存在)。因为每次都能去掉一半的内容,时间复杂度为O(log2N),简写为O(logN),默认以2为底,它优于O(N)的直接遍历。
Java
1 | public static boolean isExist(int[] arr, int num) { |
JavaScript
1 | function isExist(arr,num){ |
(2)在一个有序数组中,找到大于等于某个数的最左侧位置
假设数组中的数从小到大排列。与上个问题类似,先确定中间位置,看是否满足大于等于该数,如果满足就看左半部分,找左半部分的中间位置;反之忽略左半部分看右部,重复此过程。
Java
1 | public static int nearestIndex(int[] arr, int num) { |
JavaScript
1 | function nearestIndex(arr,num){ |
(3)局部最小值问题(长度为n的无序数组,任何相邻的数不相等)
要求在数组中找到一个局部最小值并返回该位置。首先,判断两端的值。如果arr[0] < arr[1],那么arr[0]就是局部最小值;如果不满足,查看是否arr[n-1] < arr[n-2],这样arr[n-1]就是局部最小;如果两端的值都不是局部最小值,那么说明必存在中间位置的局部最小值arr[i-1] > arr[i] < arr[i+1](因为头部呈现递减,尾部呈现递增,不可能不存在局部最小)。找中间位置,判断它是否是局部最小,如果不是并且这个局部显示递增,那么就忽略中间位置的右半部分,在它的左侧找局部最小值,反之显示递减就找右侧(和刚才原理相同,头部呈现递减尾部呈现递增那么其中一定存在局部最小值,因此又能一次去除一半的数据)。最终找到并返回该位置。
在这个例子中,数组内的数并不是有序排列的,但还是可以构建出一个每次去除一半元素的标准,所以二分法在此适用。
Java
1 | public static int getMinIndex(int[] arr) { |
JavaScript
1 | function getMinIndex(arr){ |