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

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

编号:0029

试题来源:leetcode

我的博客:Something

试题描述

给定一个数组arr和一个目标值target,请返回一个整数value,使得将数组中所有大于value的值变成value后,数组的和最接近target(最接近表示两者之差的绝对值最小)

如果有多种使得和最接近target的方案,请返回这些整数中最小的值。

请注意,value不一定是arr中的数字

  • 1 ≤ a r r . l e n g t h ≤ 1 0 4 1\leq arr.length\leq 10^4 1arr.length104
  • 1 ≤ a r r [ i ] , t a r g e t ≤ 1 0 5 1\leq arr[i],\quad target\leq10^5 1arr[i],target105

解答算法

暴力法

解答思路

首先要思考的是可能取值value的上下界,显然,当value大于arr中的最大值,那么继续增大,也不会影响数组的取值,因此value的上界应当是arr中的最大值。下界很容易想到应当是从1开始,因为arr[i]>=1

对于下界,可以做一些常数的优化,显然,假设我们有arr的大小,记为arrSize,那么显然如果会有平均数target/arrSize,那么如果arr中的最小值小于该平均数,那么当valuetarget/arrSize,整个数组的和仍然会小于target,然后我们就可以据此作为value的下界;如果整个数组中最小值都大于该平均数,那么显然,最终取值就应当是target/arrSize或者是target/arrSize + 1

代码实现

原版代码,经过了简单的优化

class Solution {
public:
    int findBestValue(vector<int>& arr, int target) {
    	map<int, int> myMap;   //定义了一个map存放arr中的值和其出现的次数
    	int arrSize = arr.size();   
    	int result, tmpSum = 0, tmpSize = 0, myMin = INT_MAX;
    	for(int i = 0; i < arrSize; ++i)   //初始化myMap
    	{
    		++myMap[arr[i]];
    	}
    	auto lastIt = myMap.begin();
    	for(auto it = myMap.begin(); it != myMap.end(); ++it)   //对myMap中的每个值进行遍历
    	{
    		int j = (it != myMap.begin() ? lastIt->first + 1 : min(target / arrSize, it->first));  //如果不是myMap中的第一个数,那么内部的遍历应当是从last到now;如果是,那么要确定起始值
    		int tmp = 0;
    		for(; j <= it->first; ++j)   //进行遍历
    		{
    			tmp = tmpSum + (arrSize - tmpSize) * j;   //如果当前取值更优,更新myMin和result,也即value
    			if(myMin > abs(target - tmp))
    			{
    				result = j;
    				myMin = abs(target - tmp);
    			}
    		}
    		tmpSum += it->first * it->second;      //每一次从新更新前缀和
    		tmpSize += it->second;                 //更新前缀的数字个数
    		lastIt = it;
    	}
    	return result;

    }
};

经过完全的优化

class Solution {
public:
    int findBestValue(vector<int>& arr, int target) {
    	int arrSize = arr.size();   
    	int result, diff = INT_MAX, cur = 0;
    	sort(arr.begin(), arr.end());   //对数组进行排序
    	int average = target / arrSize;
    	if(arr[0] > average)
    	{
    		result = abs(target - average * arrSize) > abs(target - (average + 1) * arrSize) ? (average + 1) : average;
    	}
    	else if(arr[arrSize - 1] <= average)
    	{
    		result = arr[arrSize - 1];
    	}
    	else
    	{
    		vector<int> prefix(arrSize + 1);
	        for (int i = 1; i <= arrSize; ++i)
	        {
	            prefix[i] = prefix[i - 1] + arr[i - 1];
	        }

	        for (int i = average; i <= arr[arrSize - 1] && cur < target; ++i)
	        {
	            auto iter = lower_bound(arr.begin(), arr.end(), i);
	            cur = prefix[iter - arr.begin()] + (arr.end() - iter) * i;
	            if (abs(cur - target) < diff)
	            {
	                result = i;
	                diff = abs(cur - target);
	            }
        	}
    	}
    	return result;

    }
};

优化完上下界,表现依然不是很好

双重二分查找

代码实现

class Solution {
public:
    int check(const vector<int>& arr, int x) {   //找到数组和
        int ret = 0;
        for (const int& num: arr) {
            ret += (num >= x ? x : num);
        }
        return ret;
    }

    int findBestValue(vector<int>& arr, int target) {
        sort(arr.begin(), arr.end());   //排序
        int n = arr.size();   //得到size
        vector<int> prefix(n + 1);   //前缀和数组
        for (int i = 1; i <= n; ++i) {
            prefix[i] = prefix[i - 1] + arr[i - 1];
        }

        int l = 0, r = *max_element(arr.begin(), arr.end()), ans = -1;  
        while (l <= r) {   //二分法查找
            int mid = (l + r) / 2;
            auto iter = lower_bound(arr.begin(), arr.end(), mid);
            int cur = prefix[iter - arr.begin()] + (arr.end() - iter) * mid;
            if (cur <= target) {   //找到小于等于target的最大值
                ans = mid;
                l = mid + 1;
            }
            else {
                r = mid - 1;
            }
        }
        int choose_small = check(arr, ans);
        int choose_big = check(arr, ans + 1);
        return abs(choose_small - target) <= abs(choose_big - target) ? ans : ans + 1;//进行判断
    }
};

在时间上优化了更多,因为二分查找带来的优点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值