转变数组后最接近目标值的数组和
编号: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 1≤arr.length≤104
- 1 ≤ a r r [ i ] , t a r g e t ≤ 1 0 5 1\leq arr[i],\quad target\leq10^5 1≤arr[i],target≤105
解答算法
暴力法
解答思路
首先要思考的是可能取值value
的上下界,显然,当value
大于arr
中的最大值,那么继续增大,也不会影响数组的取值,因此value
的上界应当是arr
中的最大值。下界很容易想到应当是从1开始,因为arr[i]>=1
。
对于下界,可以做一些常数的优化,显然,假设我们有arr
的大小,记为arrSize
,那么显然如果会有平均数target/arrSize
,那么如果arr
中的最小值小于该平均数,那么当value
取target/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;//进行判断
}
};
在时间上优化了更多,因为二分查找带来的优点