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;
}
};