Ежедневный бит (е) C ++ # 77, Общие проблемы на собеседованиях: минимальная стоимость для достижения равного массива

Сегодня мы рассмотрим распространенную задачу интервью C++: минимальная стоимость достижения равного массива.

Имея два массива (числа и стоимости) положительных целых чисел, определите минимальную стоимость корректировки массива чисел таким образом, чтобы все элементы имели одинаковое значение.

Стоимость корректировки каждого элемента на единицу равна соответствующему элементу в массиве затрат.

Например, для массива чисел {1,3,5,2} и массива затрат {2,3,1,14} результат равен 8. Самый дешевый целевой массив – {2,2,2,2}, это означает, что нам придется скорректировать элементы 1 и 3 один раз стоимостью 1*2+1*3 и элемент 5 три раза стоимостью 3*1, всего 8.

Прежде чем вы продолжите читать решение, я рекомендую вам попробовать решить его самостоятельно. Вот ссылка на Compiler Explorer с парой тестов: https://compiler-explorer.com/z/dedx8Yd68.

Решение

Чтобы решить эту задачу, нам понадобится немного математических знаний.

Давайте сначала рассмотрим один элемент массива. Функция, представляющая стоимость корректировки этого элемента, выпукла; то есть минимум функции - это когда мы вообще не настраиваем этот элемент и далее монотонно растет в обе стороны.

Математическая мелочь состоит в том, что сумма выпуклых функций также выпукла.

Мы можем использовать бинарный поиск, чтобы найти минимум выпуклой функции. В данном случае функция представляет собой общую стоимость, которую мы (из-за бинарного поиска) вычислим в общей сложности logk раз, где k — область значений в массиве чисел (т. е. max(числа)-min(числа)). Поскольку наша функция стоимости представляет собой простое линейное сокращение, мы получаем общую сложность O(n*logk).

#include <vector>
#include <algorithm>
#include <numeric>
#include <cstdint>

int64_t cost(const std::vector<unsigned>& nums, 
             const std::vector<unsigned>& costs,
             unsigned target) {
    return std::inner_product(nums.begin(), nums.end(),
                              costs.begin(), 
                              0LL,
                              std::plus<>{},
                              [target](int64_t num, int64_t cost) {
                                return std::abs(num-target)*cost;
                              });
}

int64_t minimum_cost(const std::vector<unsigned>& nums,
                     const std::vector<unsigned>& costs) {
    int64_t best = cost(nums, costs, nums[0]);
    // It only makes sense to search within the bounds 
    // of the existing values
    auto [left,right] = std::ranges::minmax(nums);

    // Standard binary search
    while (left < right) {
        auto mid = (left + right)/2;
        auto cost_mid = cost(nums, costs, mid);
        // However, since we are searching on a convex function
        // we have to check on which side from the minimum we are
        auto cost_right = cost(nums, costs, mid+1);
        best = std::min(cost_mid, cost_right);
        
        // Mid is on the left side from the minimum
        // and can't be the minimum
        if (cost_mid > cost_right)
            left = mid + 1;
        // Mid is on the right side of the minimum
        // and can be the minimum
        else
            right = mid;
    }
    return best;
}

Откройте решение в Compiler Explorer.