问题描述
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
示例 1:
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。
解题报告
类似于这种 最最问题 一般采用二分法。
左边界为 0
,右边界为 max(arr)
。
在二分的过程中,一直更新最接近的那个值 mid
,如果两个不同的值,其均为最接近的值,则记录较小值。
具体代码为:
if(abs(dis)<MinDis){
MinDis=abs(dis);
ans=mid;
}
else if(abs(dis)==MinDis){
ans=min(ans, mid);
}
当然了,这题还有优化空间,即左右边界移动的步长。
将
if(dis>0) r=mid-1;
else l=mid+1;
改为:
if(dis>0){
if(dis/n>0&&mid-dis/n>=l) r=mid-dis/n;
else r=mid-1;
}
else{
if(-dis/n>0&&mid-dis/n<=r) l=mid-dis/n;
else l=mid+1;
}
当前数组和 和 目标值差的较大时,考虑增大步长,满足的条件为 dis/n>0
且 不会跨越 另一边的边界。因为一旦跨越另一边的边界,则会跳出循环,有可能错过最接近的解。
实现代码
class Solution {
public:
int findBestValue(vector<int>& arr, int target) {
int n=arr.size(), MAX=INT_MIN;
for(int i=0;i<n;i++){
MAX=max(arr[i], MAX);
}
int l=0,r=MAX, mid, MinDis=INT_MAX, ans;
while(l<=r){
mid=l+(r-l)/2;
int dis=match(mid, arr, target);
if(abs(dis)<MinDis){
MinDis=abs(dis);
ans=mid;
}
else if(abs(dis)==MinDis){
ans=min(ans, mid);
}
if(dis>0) r=mid-1;
else l=mid+1;
// 时间优化
// if(dis>0){
// if(dis/n>0&&mid-dis/n>=l) r=mid-dis/n;
// else r=mid-1;
// }
// else{
// if(-dis/n>0&&mid-dis/n<=r) l=mid-dis/n;
// else l=mid+1;
// }
}
return ans;
}
int match(int mid, vector<int>& arr, int target){
int sum=0;
for(int i=0;i<arr.size();i++){
sum+=(arr[i]>mid?mid:arr[i]);
}
return sum-target;
}
};