Бинарный поиск по ответу

Базовый Algorithms
≈ 2 мин чтения

Иногда искать нужно не в массиве

Бинарный поиск применим к любому упорядоченному пространству вариантов, если для кандидата можно проверить условие и по результату понять, в какой стороне находится ответ. Частый случай — найти минимальное значение, для которого некоторое условие уже выполняется.

Представим задачу: есть несколько объёмов работы, и мы хотим найти минимальную производительность speed, при которой всю работу можно завершить не более чем за limit единиц времени. Чем больше скорость, тем легче уложиться в лимит. Значит, ответы имеют форму «не подходит, не подходит, ..., подходит, подходит».

Ищем первый подходящий ответ

static int minSpeed(int[] work, int limit) {
    int left = 1;
    int right = Arrays.stream(work).max().orElse(1);

    while (left < right) {
        int mid = left + (right - left) / 2;

        if (canFinish(work, limit, mid)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }

    return left;
}

static boolean canFinish(int[] work, int limit, int speed) {
    long time = 0;

    for (int amount : work) {
        time += (amount + speed - 1L) / speed;
    }

    return time <= limit;
}

Функция canFinish — это проверка кандидата. Если скорость mid уже подходит, ответ может быть равен ей или быть меньше, поэтому сохраняем середину через right = mid. Если не подходит, все меньшие скорости тоже бесполезны и можно перейти к mid + 1.

Главное условие — монотонность

Бинарный поиск по ответу работает, когда результат проверки меняется в одном направлении: например, сначала все кандидаты дают false, а после некоторой границы — true. Если подходящие и неподходящие варианты хаотично чередуются, отбросить половину диапазона нельзя.

Стоимость зависит от проверки

Если диапазон ответов имеет размер R, бинарный поиск выполнит O(log R) проверок. Если одна проверка canFinish проходит по n элементам, общая сложность будет O(n log R).

Перед написанием кода сформулируй две вещи: что именно является кандидатом на ответ и почему после проверки кандидата можно отбросить целую сторону диапазона. Если второй части нет, бинарный поиск здесь не обоснован.