Sum of Mutated Array Closest to Target
Binary search the cap value that, after clamping every element, makes the array sum closest to target.
5 min read
binary-search parametric-search
Problem#
Given an integer array arr and target, find a non-negative integer value such that replacing every element greater than value with value makes the sum closest to target. If two values tie, return the smaller.
Examples#
arr = [4,9,3], target = 10→3(sum becomes 3+3+3 = 9, closest to 10)arr = [2,3,5], target = 10→5arr = [60864,25176,27249,21296,20204], target = 56803→11361
Constraints#
1 <= arr.length <= 10^4,1 <= arr[i], target <= 10^5.
Approach#
The clipped sum is monotonic in value. Binary search value over [0, max(arr)] and pick the boundary point. Compare |sum(value) - target| vs |sum(value-1) - target|, returning the smaller value on tie.
Solution#
class Solution {public: int findBestValue(vector<int>& arr, int target) { auto clipSum = [&](int v) { long s = 0; for (int x : arr) s += min(x, v); return s; }; int lo = 0, hi = *max_element(arr.begin(), arr.end()); while (lo < hi) { int mid = lo + (hi - lo) / 2; if (clipSum(mid) < target) lo = mid + 1; else hi = mid; } long sHi = clipSum(lo), sLo = clipSum(lo - 1); if (abs(sLo - target) <= abs(sHi - target)) return lo - 1; return lo; }};func findBestValue(arr []int, target int) int { abs := func(x int) int { if x < 0 { return -x } return x } clipSum := func(v int) int { s := 0 for _, x := range arr { if x < v { s += x } else { s += v } } return s } lo, hi := 0, 0 for _, x := range arr { if x > hi { hi = x } } for lo < hi { mid := lo + (hi-lo)/2 if clipSum(mid) < target { lo = mid + 1 } else { hi = mid } } sHi := clipSum(lo) sLo := clipSum(lo - 1) if abs(sLo-target) <= abs(sHi-target) { return lo - 1 } return lo}from typing import List
class Solution: def findBestValue(self, arr: List[int], target: int) -> int: def clip_sum(v: int) -> int: return sum(min(x, v) for x in arr)
lo, hi = 0, max(arr) while lo < hi: mid = lo + (hi - lo) // 2 if clip_sum(mid) < target: lo = mid + 1 else: hi = mid s_hi = clip_sum(lo) s_lo = clip_sum(lo - 1) if abs(s_lo - target) <= abs(s_hi - target): return lo - 1 return lofunction findBestValue(arr, target) { const clipSum = (v) => { let s = 0; for (const x of arr) s += Math.min(x, v); return s; }; let lo = 0, hi = 0; for (const x of arr) if (x > hi) hi = x; while (lo < hi) { const mid = lo + Math.floor((hi - lo) / 2); if (clipSum(mid) < target) lo = mid + 1; else hi = mid; } const sHi = clipSum(lo); const sLo = clipSum(lo - 1); if (Math.abs(sLo - target) <= Math.abs(sHi - target)) return lo - 1; return lo;}class Solution { public int findBestValue(int[] arr, int target) { int lo = 0, hi = 0; for (int x : arr) if (x > hi) hi = x; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (clipSum(arr, mid) < target) lo = mid + 1; else hi = mid; } long sHi = clipSum(arr, lo); long sLo = clipSum(arr, lo - 1); if (Math.abs(sLo - target) <= Math.abs(sHi - target)) return lo - 1; return lo; }
private long clipSum(int[] arr, int v) { long s = 0; for (int x : arr) s += Math.min(x, v); return s; }}function findBestValue(arr: number[], target: number): number { const clipSum = (v: number): number => { let s: number = 0; for (const x of arr) s += Math.min(x, v); return s; }; let lo: number = 0, hi: number = 0; for (const x of arr) if (x > hi) hi = x; while (lo < hi) { const mid: number = lo + Math.floor((hi - lo) / 2); if (clipSum(mid) < target) lo = mid + 1; else hi = mid; } const sHi: number = clipSum(lo); const sLo: number = clipSum(lo - 1); if (Math.abs(sLo - target) <= Math.abs(sHi - target)) return lo - 1; return lo;}Editorial#
Clipping is non-decreasing in value, so the first v where clipSum(v) >= target is the binary-search boundary. The optimal answer is either that v or v-1. On a tie in distance, the smaller value wins per the problem spec.
Complexity#
- Time: O(n log M) where M = max(arr).
- Space: O(1).
Concept revision#
Related#