9、二分查找

内容来自刘宇波老师算法与数据结构体系课

1、二分查找法

/**
 * 二分查找法: O(logN)
 */
public class BinarySearch {

    private BinarySearch() {
    }

    /**
     * 二分查找法递归实现
     */
    public static <E extends Comparable<E>> int searchR(E[] data, E target) {
        return searchR(data, 0, data.length - 1, target);
    }

    private static <E extends Comparable<E>> int searchR(E[] data, int l, int r, E target) {
        if (l > r) return -1;

        int mid = l + (r - l) / 2;
        if (data[mid].compareTo(target) == 0) return mid;

        if (data[mid].compareTo(target) < 0) return searchR(data, mid + 1, r, target);
        return searchR(data, l, mid - 1, target);
    }

    /**
     * 二分查找法非递归实现
     */
    public static <E extends Comparable<E>> int search(E[] data, E target) {
        int l = 0;
        int r = data.length - 1;
        int mid;

        // 在 data[l, r] 中查找 target
        // 每次循环开始时: data[l]、data[r] 还没看, 因此 l == r 时, 依然要进入循环
        while (l <= r) {
            mid = l + (r - l) / 2;
            if (data[mid].compareTo(target) == 0) return mid;
            if (data[mid].compareTo(target) < 0) l = mid + 1;
            else r = mid - 1;
        }

        return -1;
    }
}

2、Ceil 上界

public class Ceil {

    private Ceil() {
    }

    /**
     * 在 data[] 中查找 > target 的最小值所在的索引
     */
    private static <E extends Comparable<E>> int upper(E[] data, E target) {
        int l = 0;
        int r = data.length;
        int mid;

        // 在 data[l, r] 中查找 > target 的最左边的索引, r = data.length
        // 每次循环开始时: data[l] 还没看, data[r] 可能是解, 因此当 l == r 时, r 就是解
        while (l < r) {
            mid = l + (r - l) / 2; // l <= mid < r, l 与 r 相邻时 mid = l
            if (data[mid].compareTo(target) > 0) r = mid;
            else l = mid + 1;
        }

        return r;
    }

    /**
     * 存在 target 时, 返回 = target 的最右边的索引
     * 没有 target 时, 返回 > target 的最左边的索引
     */
    public static <E extends Comparable<E>> int ceilR(E[] data, E target) {
        int upper = upper(data, target);
        if (upper - 1 >= 0 && data[upper - 1].compareTo(target) == 0) return upper - 1;
        return upper;
    }

    /**
     * 存在 target 时, 返回 = target 的最左边的索引
     * 没有 target 时, 返回 > target 的最左边的索引
     */
    public static <E extends Comparable<E>> int ceilL(E[] data, E target) {
        int l = 0;
        int r = data.length;
        int mid;

        // 在 data[l, r] 中查找 >= target 的最左边的索引, r = data.length
        // 每次循环开始时: data[l] 还没看, data[r] 可能是解, 因此当 l == r 时, r 就是解
        while (l < r) {
            mid = l + (r - l) / 2; // l <= mid < r, l 与 r 相邻时 mid = l
            if (data[mid].compareTo(target) >= 0) r = mid;
            else l = mid + 1;
        }

        return r;
    }
}

3、Floor 下界

public class Floor {

    private Floor() {
    }

    /**
     * 查找 < target 的最大值所在的索引
     */
    private static <E extends Comparable<E>> int lower(E[] data, E target) {
        int l = -1;
        int r = data.length - 1;
        int mid;

        // 在 data[l, r] 中查找 < target 的最右边的索引, l = -1
        // 每次循环开始时: data[l] 可能是解, data[r] 还没看, 因此当 l == r 时, l 就是解
        while (l < r) {
            mid = l + (r - l + 1) / 2; // l < mid <= r, l 与 r 相邻时 mid = r
            if (data[mid].compareTo(target) < 0) l = mid;
            else r = mid - 1;
        }

        return l;
    }

    /**
     * 存在 target 时, 返回 = target 的最左边的索引
     * 没有 target 时, 返回 < target 的最右边的索引
     */
    public static <E extends Comparable<E>> int floorL(E[] data, E target) {
        int lower = lower(data, target);
        if (lower + 1 < data.length && data[lower + 1].compareTo(target) == 0) return lower + 1;
        return lower;
    }

    /**
     * 存在 target 时, 返回 = target 的最右边的索引
     * 没有 target 时, 返回 < target 的最右边的索引
     */
    public static <E extends Comparable<E>> int floorR(E[] data, E target) {
        int l = -1;
        int r = data.length - 1;
        int mid;

        // 在 data[l, r] 中查找 < target 的最右边的索引, l = -1
        // 每次循环开始时: data[l] 可能是解, data[r] 还没看, 因此当 l == r 时, l 就是解
        while (l < r) {
            mid = l + (r - l + 1) / 2; // l < mid <= r, l 与 r 相邻时 mid = r
            if (data[mid].compareTo(target) <= 0) l = mid;
            else r = mid - 1;
        }

        return l;
    }
}

4、力扣问题

如果问题有明显的边界,就可以用二分查找法在这个边界去搜索问题的解

4.1、爱吃香蕉的珂珂

875 - 爱吃香蕉的珂珂

public class MinEatingSpeed {

    /**
     * piles = [3, 6, 7, 11], H = 8
     * 输出: 4
     */
    public static int minEatingSpeed(int[] piles, int h) {
        int speedL = 1;
        int speedR = Arrays.stream(piles).max().getAsInt();
        int speedMid;

        // arr[speedL, speedR] 代表吃香蕉的速度, time = eatingTime(speed)
        // 二分搜索 time <= h 最大的 time 对应 speed
        // 每次循环开始时: speedL 还没看, speedR 可能是解, 因此当 speedL == speedR 时, speedR 就是解
        while (speedL < speedR) {
            speedMid = speedL + (speedR - speedL) / 2;
            int time = getEatingTime(piles, speedMid);
            if (time <= h) speedR = speedMid;
            else speedL = speedMid + 1;
        }

        return speedR;
    }

    private static int getEatingTime(int[] piles, int speed) {
        int times = 0;
        for (int pile : piles) {
            times += pile / speed + (pile % speed == 0 ? 0 : 1);
        }
        return times;
    }

    public static void main(String[] args) {
        int[] piles1 = {3, 6, 7, 11}; // 4
        System.out.println(minEatingSpeed(piles1, 8));

        int[] piles2 = {30, 11, 23, 4, 20}; // 30
        System.out.println(minEatingSpeed(piles2, 5));
    }
}

4.2、在 D 天内送达包裹的能力

1011 - 在 D 天内送达包裹的能力

public class ShipWithinDays {

    public static int shipWithinDays(int[] weights, int days) {
        int capacityL = Arrays.stream(weights).max().getAsInt();
        int capacityR = Arrays.stream(weights).sum();
        int capacityMid;

        // arr[capacityL, capacityR] 代表运载能力, time = getShipTime(capacity)
        // 二分搜索 time <= days 最大的 time 对应 capacity
        // 每次循环开始时: capacityL 还没看, capacityR 可能是解, 因此当 capacityL == capacityR 时, capacityR 就是解
        while (capacityL < capacityR) {
            capacityMid = capacityL + (capacityR - capacityL) / 2;
            int times = getShipTime(weights, capacityMid);
            if (times <= days) capacityR = capacityMid;
            else capacityL = capacityMid + 1;
        }

        return capacityR;
    }

    private static int getShipTime(int[] weights, int maxCapacity) {
        int times = 0;
        int hasCapacity = 0;
        for (int weight : weights) {
            if (hasCapacity + weight <= maxCapacity) hasCapacity += weight;
            else {
                times++;
                hasCapacity = weight;
            }
        }
        return times + 1;
    }
}
posted @ 2023-04-11 00:05  lidongdongdong~  阅读(42)  评论(0编辑  收藏  举报