二分搜索的关键在于target、目标单调,故关键点为target、x、f(x)

故二分搜索常用于速度/能力界限问题,如每天吃几个香蕉/运多少货物

其中mid=left+(right-left)/2优于mid=(left+right)\2是因为:对于大数,相加可能溢出

左闭右开区间中,左右相等是没有意义的,所以循环中的限制条件应该是left<right

左闭右开区间最后找到的边界的值在left,找到的确值在mid,这是因为,找到对应相等时,对left的值是确界(已被更新或在equal else中被更新)

(为什么mid不是确界?因为mid只是相等,没有界限意义)

mid的值立即与target相关(equal else直接判断),若在equal else中更新left=mid,也可返回确值left

二分思维的精髓就是:通过已知信息尽可能多地收缩(折半)搜索空间,从而增加穷举效率,快速找到目标。

/**
 * 这题的x为运载能力,f(x)为运载天数,target为days
 * 因为不能拆分,运载能力至少要保证最大物品能一天送达,至多为送达所有物品,然后对于运载的包裹数组,对于运载能力进行二分查找,
 * 将运载天数与days比较,找到满足天数小于等于days的最小运载能力
 */
class Solution {
    public int shipWithinDays(int[] weights, int days) {
        int left = 0, right = 1, middle, min = Integer.MAX_VALUE;// 定义运载数量的范围,左闭右开故right初值不为0为1
        for (int i = 0; i < weights.length; i++) {
            left = Math.max(weights[i], left);
            right += weights[i];
        }
        while (left < right) {
            middle = left + (right - left) / 2;
            if (f(weights, middle) <= days) {
                min = Math.min(middle, min);
                right = middle;
            } else
                left = middle + 1;
        }
        return min;
    }

    int f(int[] weights, int x) {// 计算运载天数
        int days = 0, cap = 0;// capability
        for (int i = 0; i < weights.length;) // i代表货物下标,运到把货运完为止
        {
            cap = x;// 可运载量-初值为x
            while (i < weights.length)// 同样,此处运到把货运完为止,且运到货才会继续
            {
                if (cap < weights[i])
                    break;
                cap -= weights[i];
                i++;
            }
            days++;
        }
        return days;
    }
}