这是数组的第23篇算法,力扣链接。
给你一个长度为 偶数
n
的整数数组nums
和一个整数limit
。每一次操作,你可以将nums
中的任何整数替换为1
到limit
之间的另一个整数。如果对于所有下标
i
(下标从0
开始),nums[i] + nums[n - 1 - i]
都等于同一个数,则数组nums
是 互补的 。例如,数组[1,2,3,4]
是互补的,因为对于所有下标i
,nums[i] + nums[n - 1 - i] = 5
。返回使数组 互补 的 最少 操作次数。
示例 1:
输入:nums = [1,2,4,3], limit = 4 输出:1 解释:经过 1 次操作,你可以将数组 nums 变成 [1,2,2,3](加粗元素是变更的数字): nums[0] + nums[3] = 1 + 3 = 4. nums[1] + nums[2] = 2 + 2 = 4. nums[2] + nums[1] = 2 + 2 = 4. nums[3] + nums[0] = 3 + 1 = 4. 对于每个 i ,nums[i] + nums[n-1-i] = 4 ,所以 nums 是互补的。示例 2:
输入:nums = [1,2,2,1], limit = 2 输出:2 解释:经过 2 次操作,你可以将数组 nums 变成 [2,2,2,2] 。你不能将任何数字变更为 3 ,因为 3 > limit 。示例 3:
输入:nums = [1,2,1,2], limit = 2 输出:0 解释:nums 已经是互补的。
首先这里引入一种解题思路,即差分数组:
差分数组是一种用来表示数列的方法,它能够高效地对原始数组的某个区间进行增减操作,同时能够快速计算出区间操作后的结果。差分数组通常用于解决数据的区间更新问题。
假设有一个初始数组
A
,长度为n
,差分数组B
也是一个长度为n
的数组,其中B[i]
表示的是A[i]
和A[i-1]
的差值,即B[i] = A[i] - A[i-1]
。对于数组A
的第一个元素A[0]
,我们可以设定B[0] = A[0]
,因为没有A[-1]
。差分数组的主要优点是能够在
O(1)
的时间复杂度内对A
的连续子区间进行增减操作。如果我们想将A
中从l
到r
(包括l
和r
)的所有元素都加上一个值x
,我们只需要将B[l]
增加x
,并将B[r+1]
减去x
(如果r+1 < n
的话)。这样做的效果是,当我们通过差分数组重新计算A
的值时,只有从l
到r
的元素会被增加x
。要从差分数组
B
获取原始数组A
的值,我们只需要对B
进行一次前缀和计算即可。具体来说,A[0] = B[0]
,对于所有i > 0
,A[i] = A[i-1] + B[i]
。下面是一个简单的例子来说明差分数组的工作原理:
假设我们有一个数组
A = [2, 5, 3, 6]
,我们构建差分数组B
如下:A = [2, 5, 3, 6] B = [2, 3, -2, 3] // B[0]=A[0], B[1]=A[1]-A[0], B[2]=A[2]-A[1], B[3]=A[3]-A[2]
现在,如果我们想将
A
的第 2 个元素到第 3 个元素(从 0 开始计数)都加上 4,我们只需要对B
做以下操作:B[2] += 4 // 对应 A[2] 开始的地方加上 4 B[4] -= 4 // 对应 A[3] 后面的元素减去 4,由于这里 r+1 超出数组长度,所以这步可以省略
更新后的差分数组
B
为:B = [2, 3, 2, 3]
通过差分数组
B
,我们可以得到更新后的A
数组:A = [2, 5, 7, 10] // 通过计算前缀和得到
如你所见,
A[2]
和A[3]
都成功地加上了 4。差分数组是一种非常实用的数据结构,特别适用于那些需要频繁进行区间更新操作的场景。
有了差分数组,我们可以更直观的分析索引之间的数据值的变化,在这道题里可以充分应用。
然后开始分析题目:
假设数组左右的值为a,b,最终的目标和为target,分类讨论一下需要修改的次数:
- 当 target < 1 + a 时,需要操作两次;
- 当1 + a ≤ target < a + b 时,需要操作一次;
- 当 target = a+b 时,不需要操作;
- 当 a + b < target ≤ b + limit 时,需要操作一次;
- 当 target > b + limit 时,需要操作两次。
然后我们可以以最坏的情况做减法求取最小的修改次数:
- 在 1+ a 处,操作次数减少一次;
- 在 a + b 处,操作次数减少一次;
- 在 a + b + 1 处,操作次数增加一次;
- 在 b + limit + 1 处,操作次数增加一次。
因此,我们可以遍历数组中的所有数对,求出每个数对的这四个关键位置,然后更新差分数组。最后,我们遍历(扫描)差分数组,就可以找到令总操作次数最小的target,同时可以得到最少的操作次数。
func minMoves(nums []int, limit int) int {
count := make([]int, limit*2+2)
for i := 0; i < len(nums)/2; i++ {
sum := nums[i] + nums[len(nums)-1-i]
left := getMin(nums[i], nums[len(nums)-1-i])
right := sum - left + limit
count[left+1]--
count[sum]--
count[sum+1]++
count[right+1]++
}
result, cur := len(nums), len(nums)
for i := 2; i <= limit*2; i++ {
cur += count[i]
result = getMin(result, cur)
}
return result
}
func getMin(a, b int) int {
if a < b {
return a
}
return b
}