【LeetCode】1300. 转变数组后最接近目标值的数组和

1300. 转变数组后最接近目标值的数组和

返回一个序列中满足某个条件的最小值,那么就可以想到二分。

int l, r;
while (l < r) {
    int mid = l + r >> 1;
    if (check(mid)) r = mid;
    else l = mid + 1;
    return l;  // l就是满足check(mid)的第一个值
}

先对原数组排序。

那么下一步就是思考二分的区间,按照题目的意思,可以直接将l设为a[0]r设为a[n - 1]。但是有点太暴力了。

其实想一想,我们可以发现 选组数组中两个相邻数(a[k - 1], a[k])之间的所有数(t)的数组和表达式:

$ a[0] + a[1] + ... + a[k - 1] + t * (n - k)$中,(0, k - 1)的前缀和sum,与t的项数n - k是固定的。

这样我们就可以在一个固定区间内,用一个固定的表达式,用二分法求出最优解。

进一步,我们还可以确定出最优解一定在某一个区间中。假设k是满足$ (a[0] + a[1] + ... a[k - 1]) + a[k] * (n - k) >= target\(的最小值,那么答案就一定在\)[a[k-1], a[k]]$中。

class Solution {
public:

    int findBestValue(vector<int>& a, int target) {
        int n = a.size();

        sort(a.begin(), a.end());
        
        int k = 0, sum = 0;
        // 找到二分搜索区间, k是满足 (a[0] + a[1] + ... a[k - 1]) + a[k] * (n - k) >= target的最小值
        // 那么答案一定在[a[k - 1], a[k]] 中
        while ( k < n && a[k] * (n - k)  + sum < target) {  
            sum += a[k];
            k++;
        }
        
        if (k >= n) return a[n - 1];
        
        int l = 0, r = a[k];
        if (k) l = a[k - 1];
        
        while (l < r) {  
            int mid = l + r >> 1;
            int val = sum + mid * (n - k);
            if (val >= target) r = mid;
            else l = mid + 1;
        }
        
        // l 是 >= target的第一个数, l - 1是 < target的第一个数
        int left = abs(sum + (l - 1) * (n - k) - target), right = abs(sum + l * (n - k) - target);

        if (left <= right) return l - 1;
        else return l;
    }
};
posted @ 2021-02-06 22:15  macguz  阅读(117)  评论(0编辑  收藏  举报