二分查找,就是通过每次将查找范围缩小一半的方法,最终锁定目标。话不多说,直接看最基础的二分查找模型

二分查找基础模型

输入:
一个有序数组,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。为了实现二分查找,我们需要确定三个东西:

  1. L所在的范围
  2. L需要满足的条件(用于判断是否已经找到)
  3. 查找精度(和基础模型不同,我们的查找并非是在一个数组中查找,而是在一个数字区间内查找,所以我们需要一个查找精度,用来将数字区间转化为步长为精度的数组)
  • 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;
    }

相关文章: