二分查找,就是通过每次将查找范围缩小一半的方法,最终锁定目标。话不多说,直接看最基础的二分查找模型
二分查找基础模型
输入:
一个有序数组,int[] arr
一个目标值,int key
输出:
目标值的索引位置
我们通过图解来理解二分查找
二分查找本质上是一个递归查找的过程,每一次的递归运算分三步
第一步,判断数组的正中心(center)是否为key,如果是,输出结果,如果不是,进入下一步
注:当数组size为偶数,也就是说没有中心的时候,我们任选中心两个数据之一作为中心
在这个例子中,显然中心arr[8]不等于key
第二步,判断key在center的左边还是右边
因为这是一个有序数组,我们可以通过判断大小,判断左右。在这个例子中key<arr[8],key在center的左边。
第三步,在左边,将数组右端点更新为center;在右边,将数组的左端点更新为center。递归
这时我们可以忽略掉arr[8]已经其之后的数据,在下一次的递归中,在0-7的范围内重复上面的过程。
余下过程
代码实现:
public class BinarySearch {
public static int binarySearch(int[] arr, int key) {
int left = 0;
int right = arr.length - 1;
return binarySearch(arr, key, left, right);
}
private static int binarySearch(int[] arr, int key, int left, int right) {
if (left == right)
return left;
int mid = (left + right) / 2;
if(arr[mid]==key)
return mid;
if(arr[mid]>key)
return binarySearch(arr,key,left,mid-1);
else
return binarySearch(arr,key,mid+1,right);
}
}
当然,使用while循环同样可以完成,这里不再赘述
进阶题目
有N根绳子,第i根绳子长度为Li,现在需要M根等长的绳子,你可以对n根绳子进行任意裁剪(不能拼接),请你帮忙计算出这m根绳子最长的长度是多少。
输入:
绳子根数 int N
需要的绳子根数 int M
储存着每根绳子长度的数组 int[] lengths
输出:
最长的长度 L
分析
假设最终结果L是在一个范围内,这样,我们只需要通过二分查找,就能找到L。为了实现二分查找,我们需要确定三个东西:
- L所在的范围
- L需要满足的条件(用于判断是否已经找到)
- 查找精度(和基础模型不同,我们的查找并非是在一个数组中查找,而是在一个数字区间内查找,所以我们需要一个查找精度,用来将数字区间转化为步长为精度的数组)
-
L所在的范围
显然,L最小应该为0。那么,最大呢?分析题目可以得知,最大应该是所有绳子中最长的那根的长度,因为如果再大,我们就不可能得到长度为L的绳子了,因为所有的绳子都没有L长。
left = 0
right = max{lengths[i]} -
L需要满足的条件
这是这道题的核心,审题我们可以得出L需要满足两个基本条件:一是能够通过切割得到M根L长度的绳子;二是L要是最长的可能性。第一个条件很容易写出,只要计算每一根绳子最多能够分为m根L长度的绳子,然后将每根绳子的m想加,如果大于等于M,则条件一满足。那么,条件二呢?
当我们判断一个L满足条件1时,那么他就是合法的,我们只想要更大的L,也就是说,目标值一定大于等于当前L,所以我们只需要不断向右寻找即可。而当L不满足条件1时,说明L过长,我们只需要向左寻找。这样,二分条件就完成了。
public static double binarySearch(int[] arr, int N, int M) {
double esp = 0.00000001;
double left = 0;
double right = 0;
for (int i = 0; i < arr.length; i++) {
if (right < arr[i])
right = arr[i];
}
double mid = (left + right) / 2;
while (right - left < esp) {
if (isLegal(arr, mid, M, N))
left = mid;
else
right = mid;
}
return mid;
}
private static boolean isLegal(int[] arr, double mid, int m, int n) {
int amount = 0;
for (int i = 0; i < n; i++) {
amount+=(arr[i]/mid);
}
return amount >= m;
}