📅  最后修改于: 2023-12-03 15:12:50.700000             🧑  作者: Mango
有一个长度为 n 的整数数组 nums,以及一个整数 k。我们可以进行任意次数的操作,每次操作中,选择数组中的一个元素,将它加上或者减去一个整数 x(x 可以为负),最终使数组中的每个元素都等于 k。需要从任一端减去最小数组元素以将 k 减少到 0。
我们可以先找出数组中的最小值,然后将这个最小值从每个元素中减去,并且将 k 减去这个最小值。这样做之后,我们可以发现数组中的最小值为 0,k 也相应地变成了 k - minNum,其中 minNum 是数组中的最小值。
现在问题转化成了,我们如何在数组中任意选择一个元素进行加减,使得数组中的所有元素都变成 0。那么我们只需要将数组中的每个元素都减去 k 即可,此时数组中所有的元素都变成了 0。
最终需要的操作次数就是最小数组元素减去 k 的绝对值的和。
def minMoves(nums: List[int], k: int) -> int:
idx = []
for i, num in enumerate(nums):
if num == 1:
idx.append(i - len(idx))
n = len(idx)
preSum = [0] * (n + 1)
for i in range(n):
preSum[i + 1] = preSum[i] + idx[i]
l, r = 0, k // 2
ans = float('inf')
while r < n:
mid = (l + r) // 2
ans = min(ans, preSum[r + 1] - preSum[mid + 1] - preSum[mid] + preSum[l])
l += 1
r += 1
return ans
nums = [2,3,4,1,5]
k = 3
result = minMoves(nums, k)
print(result)
上面给出的是一个二分查找的做法。主要思路是将 1 的下标记录下来,然后计算前缀和,最后用二分查找找到最优解。
具体地,我们将所有 1 的下标保存在数组 idx 中。然后计算前缀和 preSum,表示从 0 到 i 的下标中间有多少个数要向左或向右移动才能到达 k。
对于任意一个区间 [i, j],如果我们对区间中间的元素进行操作,以 1 为中心向两边推进,就可以将区间中间的元素都变为 1。同时,因为要从任一端减去最小数组元素以将 k 减少到 0,所以我们选择中间点,使得左右两侧的点数相等。
具体地,我们维护左右两个指针 l 和 r,初始值分别为 0 和 k / 2。然后我们通过二分查找,找到一个使得区间 [l, r] 中间的元素个数相等的 mid。然后用前缀和计算左侧和右侧的移动距离,最终取最小值。
代码的时间复杂度为 O(n log n),空间复杂度为 O(n)。