差分数组是什么
差分数组是原数组相邻两数的差值构成的数组。利用差分数组,可以快速地实现对区间内所有数同时加(减)去某个数。这是因为差分数组维护是原数组的变化量,在每次同时加(减)操作之后,除了差分数组两端的值会发生变化,差分数组内部的值保持不变。引用一位大神博客中的例子具体说明:
来源:https://note.cser.club/algorithm/untitled
原始数组和差分数组
将区间[1,4]中元素同时+3
这时我们就会发现这样一个规律,当对一个区间进行增减某个值的时候,他的差分数组对应的区间左端点的值会同步变化,而他的右端点的后一个值则会相反地变化,其实这个很好理解。
题目描述
给你一个长度为 偶数 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]提示:
- n == nums.length
- 2 <= n <= 1 0 5 10^5 105
- 1 <= nums[i] <= limit <= 1 0 5 10^5 105
- n 是偶数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-moves-to-make-array-complementary
解题思路
由已知条件可知,互补的两数之和S = nums[i]+nums[n-1-i]满足条件2 ≤ \leq ≤ S ≤ \leq ≤ 2*limit,因此只需将[2, 2*limit]中的每一个数作为互补和求出对应的操作次数,取最小值即可。
对于每一对a = nums[i]和b = nums[n-1-i],要是它们互补,需要的操作次数可能为2、1、0。当 a+b == S时,操作次数为0;当 1+min(a,b) <= S <= limit+max(a,b)时,操作次数为1(只需换一个);当 2 <= S <= 2*limit时,操作次数为2(两个都要换)。
通过以上分析,可以通过构造差分数组times[2*limit+2]来完成对区间的同时操作,times[i]在差分数组的原数组中表示将i当作互补和时对应的操作次数。具体算法:遍历数组nums,对于每一对a和b,
- 将区间[2, 2*limit]的数都+2,对应于2个操作次数,
- 将区间[1+min(a,b), limit+max(a,b)]的数都-1,对应于1个操作次数,
- 将区间[a+b, a+b]的数都-1,对应于0个操作次数,
最后将差分数组恢复成原数组,取最小值即可。算法的时间复杂度为 O ( n ) O(n) O(n)。
代码
class Solution {
public:
int minMoves(vector<int>& nums, int limit) {
int n = nums.size(), ans = nums.size(), sum = 0;
vector<int> times(2*limit+2); //建立差分数组,大小2*limit+2
for(int i = 0; i < n/2; i++){
int a = nums[i], b = nums[n-1-i];
//将区间[2, 2*limit]的数都+2
times[2] += 2;
//将区间[1+min(a,b), limit+max(a,b)]的数都-1
times[1+min(a,b)] -= 1;
times[1+limit+max(a,b)] += 1;
//将区间[a+b, a+b]的数都-1
times[a+b] -= 1;
times[1+a+b] += 1;
}
//将差分数组恢复成原数组,最小值为答案
for(int i = 2; i <= 2*limit; i++){
sum += times[i];
ans = min(ans, sum);
}
return ans;
}
};