[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/ntarget/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/

https://leetcode.com/problems/sum-of-mutated-array-closest-to-target/discuss/463306/JavaC%2B%2BPython-Just-Sort-O(nlogn)

https://leetcode.com/problems/sum-of-mutated-array-closest-to-target/discuss/470114/C%2B%2B-binary-search-%2B-get-the-lowest-point


LeetCode All in One 题目讲解汇总(持续更新中...)

posted @ 2022-05-11 13:51  Grandyang  阅读(347)  评论(0编辑  收藏  举报
Fork me on GitHub