转变数组后最接近目标值的数组和
leetcode 1300
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
示例 1:
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。
示例 2:
输入:arr = [2,3,5], target = 10
输出:5
二分查找 + 枚举
我们可以枚举出所有的范围,再进行比较筛选。
由于需要求得的结果是整数,所以可以从 0 开始枚举。
枚举的上届是最大值,因为当 value >= arr
时,数组中所有的元素都不变。就不需要再进行下去了,所得结果不会有变化。
当枚举到 value = x 时,我们需要将数组 arr 中所有小于等于 x 的值保持不变,所有大于 x 的值变为 x。要实现这个操作,我们可以将数组 arr 先进行排序,随后进行二分查找,找出数组 arr 中最小的比 x 大的元素 arr[i]。此时数组的和变为:
a
r
r
[
0
]
+
.
.
.
+
a
r
r
[
i
−
1
]
+
x
∗
(
n
−
i
)
arr[0]+...+arr[i−1]+x∗(n−i)
arr[0]+...+arr[i−1]+x∗(n−i)
为了加速求和操作,我们可以预处理出数组 arr 的前缀和,这样数组求和的时间复杂度即能降为 O ( 1 ) O(1) O(1)。我们将和与 target 进行比较,同时更新答案即可。
Python 代码如下所示:
def findBestValue(self, arr: List[int], target: int) -> int:
n = len(arr)
arr.sort()
prefix = [0] # 前缀和,并在前面补 [0]
for num in arr:
prefix.append(prefix[-1] + num)
res, diff = 0, float('inf')
for num in range(0, arr[-1]+1): # 枚举
idx = bisect_left(arr, num)
cur = prefix[idx] + (n-idx) * num
if abs(cur - target) < diff:
res, diff = num, abs(cur - target)
return res
注意前缀和的构造,第一位是 0,长度是 n + 1 n+1 n+1。实际上最后一位并非前缀和,只是数组的和。
扩展:bisect
模块的用法
bisect
全称为 bisection (二分)算法。
主要有两种用法,6 个函数:
-
找到在有序数组的插入点位置,以维持有序。
bisect.bisect_left(a, x, lo=0, hi=len(a))
最后得出的结果在[lo, hi]
区间内,注意是闭区间,得出hi
结果还要判断是否能用。
如果 x 已经在 a 里存在,那么插入点会在已存在元素之前(也就是左边)。bisect.bisect(a, x, lo=0, hi=len(a))
等价于bisect.bisect_right(a, x, lo=0, hi=len(a))
,与上述函数的区别是:
返回的插入点是 a 中已存在元素 x 的右侧。
-
将数插入到一个有序序列里,并维持其有序。
bisect.insort_left(a, x, lo=0, hi=len(a))
:相当于a.insert(bisect.bisect_left(a, x, lo, hi), x)
。要注意搜索是 O ( log n ) O(\log n) O(logn) 的,插入却是 O ( n ) O(n) O(n) 的。bisect.insort(a, x, lo=0, hi=len(a))
等价于bisect.insort_right(a, x, lo=0, hi=len(a))
,与上面区别是把 x 插入到 a 中已存在元素 x 的右侧。
简单来说 bisect_left
和 bisect
用于得到索引位置,insort_left
和 insort
用于插入数据,长度要增加 1。