二分查找算法应用总结
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。请注意,答案不一定是 arr 中的数字。
力扣(LeetCode)第1300题
①.最小值
②.最大值
value 的最大值为数组 arr 中的最大值。因为当value 大于等于 arr 中的最大值时,数组中的元素不发生变化,对数组的和不再有影响
在 value 的取值范围之内,随着 value 的增大,数组的和是单调递增的。因此在一个有序序列中查找某个值,使用二分查找。
①.等于 target 时:当存在数组和等于 target 时,此为理想情况,绝对的最接近;
②.大于 target 时:当所有的取值都使数组和大于 target 时,此时需要找一个最小的值,使更接近target ;
③.大于 target 时:当所有的取值都使数组和小于 target 时,此时需要找一个最大的值,使更接近target ;
综上,可以采用两遍二分查找,分别找出 >=target 和 <=target 时最接近的值,然后取最优结果。
当找到一个 value 时,把 arr分为两部分,前半部分元素值不变,后半部分需要替换成 value ;
①.找分隔点
可以对 arr 排序后,使用二分查找,找到第一个大于 value 的位置;
②.前半部分求和
前半部分元素值不变,可以通过提前构建前缀和,然后直接得到;
②.后半部分求和
后半部分可以根据分隔位置计算出要替换的数量,相乘直接得到。
class Solution {
public:
int findBestValue(vector<int>& arr, int target) {
sort(arr.begin(),arr.end());//先排序,方便查找
vector<int> presum(arr.size(),0);//前缀和
presum[0] = arr[0];
for(int i = 1;i < arr.size();i++)
{
presum[i] = presum[i-1]+arr[i];
}
int left = 0,right = arr.back();//目标值最小取0,最大取arr中最大值
int lowertarget = 0,lowerSum = 0;// <= tager 时,最接近 tager的值 及 和
//二分查找
while( left <= right )
{
int mid = left + (right - left ) /2;
vector<int>::iterator it = upper_bound(arr.begin(),arr.end(),mid);//第一个大于mid的位置
int cursum = 0;//求和
if (it == arr.end() )
{
cursum = presum[arr.size() - 1];
}
else if (it == arr.begin())
{
cursum = arr.size()*mid;
}
else
{
cursum = presum[it - arr.begin()-1] + (arr.end() - it) *mid;
}
if( cursum > target )
{
right = mid-1;
}
else if( cursum < target )// 找到一个比 target 小的值,缩小范围,优化这个值使更解决 target
{
lowertarget = mid;
lowerSum = cursum;
left = mid+1;
}
else // cursum == target,肯定为最解决,直接返回
{
lowertarget = mid;
lowerSum = cursum;
break;
}
}
left = 0, right = arr.back();//初始化左右指针
int uppertarget,upperSum = 0;//>=tager 时,最接近 tager的值 及 和
while( left <= right )
{
int mid = left + (right - left ) /2;
vector<int>::iterator it = upper_bound(arr.begin(),arr.end(),mid);//第一个大于mid的位置
int cursum = 0;//求和
if (it == arr.end() )
{
cursum = presum[arr.size() - 1];
}
else if (it == arr.begin())
{
cursum = arr.size()*mid;
}
else
{
cursum = presum[it - arr.begin()-1] + (arr.end() - it) *mid;
}
if( cursum > target ) // 找到一个比 target 大 的值,缩小范围,优化这个值使更解决 target
{
right = mid-1;
uppertarget = mid;
upperSum = cursum;
}
else if( cursum < target )
{
left = mid+1;
}
else // cursum == target,肯定为最解决,直接返回
{
uppertarget = mid;
upperSum = cursum;
break;
}
}
return abs(lowerSum - target) > abs(upperSum - target) ? uppertarget : lowertarget;
}
};