[LeetCode] 1300. Sum of Mutated Array Closest to Target 转变数组后最接近目标值的数组和
Given an integer array arr
and a target value target
, return the integer value
such that when we change all the integers larger than value
in the given array to be equal to value
, the sum of the array gets as close as possible (in absolute difference) to target
.
In case of a tie, return the minimum such integer.
Notice that the answer is not neccesarilly a number from arr
.
Example 1:
Input: arr = [4,9,3], target = 10
Output: 3
Explanation: When using 3 arr converts to [3, 3, 3] which sums 9 and that's the optimal answer.
Example 2:
Input: arr = [2,3,5], target = 10
Output: 5
Example 3:
Input: arr = [60864,25176,27249,21296,20204], target = 56803
Output: 11361
Constraints:
1 <= arr.length <= 10^4
1 <= arr[i], target <= 10^5
这道题给了我们一个数组,还有一个目标值 target,现在让返回一个最小的数字 value,使得将原数组中大于 value 的数字均变为 value,且改变后的数组之和要最接近于 target,即差的绝对值最小。注意题目中也提示了返回的结果并不一定是数组中的数字,否则的话就是一道 Easy 的题目。首先来分析一下返回结果的可能范围,数组中的数字与 target 的范围都是在 [1, 10^5] 之间,那么返回数字的最小值是什么呢,大家可能会下意识的说是1,其实是0,比如这个例子 [1, 1, 1], target=1,那么此时的结果就是0。再来分析一下最大值是什么呢,肯定不会超过 10^5,再精确一点呢?其实不能超过 target,为了分析出返回结果的最大值 mx,来想什么情况下会出现最大值,应该是数组只有一个数字 num 的时候(多个数字的话 mx 只会更小),这样若 num 大于 target 的话,那么 mx 就应该取和 target 相等,这样 num 就可以变为 target,从而距离最小。若 num 小于 target,则此时 mx 应该取 num 的值,因为题目中要求返回值尽可能的小。综上,mx 最多取到 target。
分析出了返回数字的范围 [0, target],就要考虑如何找出最小的符合要求的数字。当然最笨的方法就是暴力搜索,一个一个的验证,但是完全没有必要,我们可以用二分搜索法来提高查找效率。二分搜索法的难点在于如何进行折半查找,到底是去左半段还是右半段,依据什么条件。这里用到的方法实际上是博主之前总结帖 LeetCode Binary Search Summary 二分搜索法小结 中的第四类-用子函数当作判断关系。这里关心的是对于每个候选值,改变后的数组之和跟 target 之间的距离,可以放到一个子函数中来计算,只要遍历 arr 数字,每次累加遍历到的数字 num 和 mid 之间的较小值即可,最后返回 sum 和 target 的差的绝对值。分别对 mid 和 mid+1 计算一下,若 mid 计算出的 diff 要大,说明此时 mid 偏小了,left 更新为 mid+1,否则 right 更新为 mid,最后返回 left 即可,参见代码如下:
解法一:
class Solution {
public:
int findBestValue(vector<int>& arr, int target) {
int n = arr.size(), left = 0, right = target;
while (left < right) {
int mid = left + (right - left) / 2;
if (diff(mid, arr, target) > diff(mid + 1, arr, target)) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
int diff(int mid, vector<int>& arr, int target) {
int sum = 0;
for (int num : arr) {
sum += min(num, mid);
}
return abs(sum - target);
}
};
再来看一种不用二分搜索法的解法,博主感觉有点类似贪婪算法的感觉。由于遇到比 value 大于的数字要变为 value,那么可以给原数组排个序,这样若当前数字大于 value 了,那么之后的数字肯定更大,则可以一下子算出后面的数字之和(因为后面的大数都会变为 value,乘以后面数字的个数就行了)。
对于数组中最小的数字来说,假设最终返回的是这个数字,则后面所有的大数都会变成这个数字,那么一下就可以算出改变后的数字之和 arr[0] * n
,若这个和大于 target,说明最终返回结果可以更小,则结果一定是 target/n
和 target/n + 1
中的一个(因为 target 可能无法整除n)。若和小于 target,说明返回的结果一定比 arr[0] 大,此时可以用 target 减去 arr[0],则新的输入参数 (1, n-1) 范围的子数组和 target-arr[0] 得到的结果跟原来的相同。
一直用这种方法去检验,直到某个位置无法满足 target 大于 arr[i] * (n-i)
时循环退出,若此时i等于n了,说明所有的数字都减完了,target 还大于0,此时返回的结果就是数组中的最大数字 arr[n-1]。若在中间某个位置停了,则结果就在 target/(n-i)
和 target/(n-i) + 1
中了,参见代码如下:
解法二:
class Solution {
public:
int findBestValue(vector<int>& arr, int target) {
int n = arr.size(), i = 0;
sort(arr.begin(), arr.end());
while (i < n && target > arr[i] * (n - i)) {
target -= arr[i++];
}
if (i == n) return arr[n - 1];
int res = target / (n - i);
if (target - res * (n - i) > (res + 1) * (n - i) - target) {
++res;
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1300
参考资料:
https://leetcode.com/problems/sum-of-mutated-array-closest-to-target/