力扣刷题-1300. 转变数组后最接近目标值的数组和

给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
来源:力扣(LeetCode)

  1. value 的取值范围
    ①.最小值
      value 的最小值为 0。因为当 value = 0 时,数组的和为 0,当 value 继续减小时,数组的和也会随之减小,且变为负数不会比 value = 0 时更接近 target。
    ②.最大值
      value 的最大值为数组 arr 中的最大值。因为当value 大于等于 arr 中的最大值时,数组中的元素不发生变化,对数组的和不再有影响。
  2. 二分查找
    在 value 的取值范围之内,随着 value 的增大,数组的和是单调递增的。因此在一个有序序列中查找某个值,使用二分查找。
  3. 最接近的 target 值
    ①.等于 target 时:当存在数组和等于 target 时,此为理想情况,绝对的最接近;
    ②.大于 target 时:当所有的取值都使数组和大于 target 时,此时需要找一个最小的值,使更接近target ;
    ③.大于 target 时:当所有的取值都使数组和小于 target 时,此时需要找一个最大的值,使更接近target ;
    综上,可以采用两遍二分查找,分别找出 >=target 和 <=target 时最接近的值,然后取最优结果。
  4. 对arr求和
    当找到一个 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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值