力扣【二分查找】:1300. 转变数组后最接近目标值的数组和(官方题解学习记录)

  1. 题目描述:

给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。

如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。

请注意,答案不一定是 arr 中的数字。

  1. 算法思路:

官方题解一共有两种方法,都涉及到对数组的排序与前缀和数组的使用,

第一种方式是对value枚举,对数组排序后就可以通过二分查找找到第一个满足不小于value的元素的位置,然后结合前缀和数组就可以O(1)的时间复杂度计算出数组和,

枚举所有情况时只在绝对值减小的时候更新,由于数组已经有序,所以后枚举到的数即便可以使绝对值与之前的绝对值相等,其枚举的value也依然大于之前的value,可以不用更新。

第二种方法是考虑在value的范围[0, max_elem]特别大时,枚举次数变多,而由于value枚举的范围是一个有序的范围,并且在value替换arr的成员后,其结果一定是一种严格的单调递增,

这里进行一个简单证明:

按照题目意思,设f(value) = Σmin(arr[i], value),因为value∈N,可以得到f(value + 1) = Σmin(arr[i], value + 1),

于是f(value + 1) - f(value) = Σ [min(arr[i], value + 1) - min(arr[i], value)],

设y = [min(arr[i], value + 1) - min(arr[i], value)],对每一个arr[i]有:

arr[i] >= value + 1,y = value + 1 - value = 1

arr[i] <= value,y = value - value = 0

而在arr一定存在至少一个max_elem > value,所以f(value + 1) - f(value) >= 1,所以f(value)随着value增长而严格单调递增,

所以当二分选取value时,可以确保每次选取value替换后的数组和都不一致,这样就可以通过两侧二分查找,外层是对value范围的二分查找,

内层是通过二分查找找到arr种第一个大于等于value的值的下标,

我们需要找到的是这样的两个value,f(value_lower) < target且绝对值是所有f(value_lower) < target中最小的,

f(value_upper) > target且绝对值是所有f(value_upper) > target中最小的,那么一定有value_lower + 1 = value_upper,通过反证法可以很容易得证,

所以只需要进行一次对value_lower的查找,然后比较f(value_lower)和f(value_lower + 1)谁与target更接近即可。

  1. 代码:

方法一:
时间复杂度:O((N + C)log(N)),其中N为arr长度,排序O(N log N),C为value的边界,枚举的每次都需要做二分查找 O(C log N)
空间复杂度:O(N),需要辅助的前缀和数组

int findBestValue(vector<int>& arr, int target) {
        std::sort(arr.begin(), arr.end());
        int size = arr.size();
        std::vector<int> prefix(size + 1);
        for (int idx = 1; idx <= size; ++idx) {
            prefix[idx] = prefix[idx - 1] + arr[idx - 1];
        }

        int upper_bound = arr[size - 1];
        int ans = 0;    //实际的lower_bound
        int diff = target;
        for (int value = 1; value <= upper_bound; ++value) {
            int idx = BinarySearch(arr, value);

            int sum = prefix[idx] + (size - idx) * value;

            if (std::abs(sum - target) < diff) {
                ans = value;
                diff = std::abs(sum - target);
            }
        }

        return ans;
    }

    int BinarySearch(vector<int> &arr, int value) {
        int left = 0, right = arr.size() - 1;

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

            if (arr[mid] < value) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }

        return left;
    }

方法二:

时间复杂度:O(N log N),两层二分的时间复杂度应该是O(log(C) * log(N)),低于O(N log N),

空间复杂度:O(N)

int findBestValue(vector<int>& arr, int target) {
        std::sort(arr.begin(), arr.end());
        int size = arr.size();
        std::vector<int> prefix(size + 1);
        for (int idx = 1; idx <= size; ++idx) {
            prefix[idx] = prefix[idx - 1] + arr[idx - 1];
        }

        int lower_bound = 0, upper_bound = arr[size - 1];
        int value_lower = 0;
        int diff = target;

        while (lower_bound < upper_bound) {
            int value = lower_bound + (upper_bound - lower_bound) / 2;

            int idx = BinarySearch(arr, value);
            int sum = prefix[idx] + (size - idx) * value;

            if (sum < target) {
                lower_bound = value + 1;
                value_lower = value;
            } else {
                upper_bound = value;
            }
        }

        int lower_abs = std::abs(CalculateSum(arr, value_lower) - target);
        int upper_abs = std::abs(CalculateSum(arr, value_lower + 1) - target);

        if (lower_abs <= upper_abs) {
            return value_lower;
        }

        return value_lower + 1;
    }

    int CalculateSum(vector<int> &arr, int value) {
        int sum = 0;
        for (const int &num : arr) {
            sum += std::min(num, value);
        }

        return sum;
    }

    int BinarySearch(vector<int> &arr, int value) {
        int left = 0, right = arr.size() - 1;

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

            if (arr[mid] < value) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }

        return left;
    }
posted @ 2026-08-13 14:40  lf123z  阅读(1)  评论(0)    收藏  举报