差分数组

5615. 使数组互补的最少操作次数

今天在做一道题的时候
在这里插入图片描述
本周周赛题,在做题时忽略了上面的这个条件,求解答案总是超时。看题解用到差分数组,之前做题一直没有接触过。在此记录以便复习。
差分数组:差分数组就是原始数组相邻元素之间的差。
在这里插入图片描述
举个栗子,给出一个差分数组在这里插入图片描述
其实差分数组是一个辅助数组,从侧面来表示给定某一数组的变化,一般用来对数组进行区间修改的操作

还是上面那个表里的栗子,我们需要进行以下操作:

1、将区间【1,4】的数值全部加上3

2、将区间【3,5】的数值全部减去5

你可以进行枚举。但是如果给你的数据量是1e5,操作量1e5,限时1000ms你暴力枚举会超时?这时我们就需要使用到差分数组了。

其实当原始数组中元素同时加上或者减掉某个数,那么他们的差分数组其实是不会变化的。

利用这个思想,咱们将区间缩小,缩小的例子中的区间 【1,4】吧这是你会发现只有 d[1]和d[5]发生了变化,而d[2],d[3],d[4]却保持着原样,
在这里插入图片描述
在进行下一个操作:
在这里插入图片描述
这时我们就会发现这样一个规律,当对一个区间进行增减某个值的时候,他的差分数组对应的区间左端点的值会同步变化,而他的右端点的后一个值则会相反地变化,其实这个很好理解

差分数组作用:
既然我们要对区间进行修改,那么差分数组的作用一定就是求多次进行区间修改后的数组

注意 :只能是区间元素同时增加或减少相同的数的情况才能用

因为我们的差分数组是由原始数组的相邻两项作差求出来的,即 d[i]=a[i]-a[i-1];那么我们能不能反过来,求得一下修改过后的a[i]呢?

直接反过来即得 a[i]=a[i-1]+d[i]

差分数组讲解完了,我们开始求解上面的问题:

假设 res[x] 表示的是,nums[i] + nums[n - 1 - i] 为 x 的时候,需要多少次操作。
我们只需要计算出所有的 x 对应的 res[x], 取最小值就好了。
根据题意,nums[i] + nums[n - 1 - i] 最小是 2,即将两个数都修改为 1;最大是 2 * limit,即将两个数都修改成 limit。
所以,res 的取值范围是 [2, 2 * limit]。我们用一个 res[2 * limit + 1] 的数组就好。
关键是,如何求出每一个 res[x] 位置的值,即修改后互补的数字和为 x,需要多少操作?

为了叙述方便,假设 nums[i] 为 A;nums[n - 1 - i] 为 B。

显然有:

如果修改后两个数字的和是 A + B,我们使用的操作数是 0 (没有修改));

否则的话,如果修改后两个数字和在 [1 + min(A, B), limit + max(A, B)] 的范围,我们使用的操作数是 1 (只需要修改 A 或者 B 就好);

否则的话,如果修改后两个数字和在 [2, 2 * limit] 的范围,我们使用的操作数是 2;

所以,我们的算法是遍历每一组 nums[i] 和 nums[n - 1 - i],然后:

先将 [2, 2 * limit] 的范围需要的操作数 + 2;

之后,将 [1 + min(A, B), limit + max(A, B)] 的范围需要的操作数 - 1(即 2 - 1 = 1,操作 1 次);

之后,将 [A + B] 位置的值再 -1(即 1 - 1 = 0,操作 0 次)。

可以看出,整个过程都是在做区间更新。最后,我们查询每一个位置的值,取最小值就好。
简单来说,差分数组 diff[i],存储的是 res[i] - res[i - 1];而差分数组 diff[0…i] 的和,就是 res[i] 的值。

大家可以用一个小数据试验一下,很好理解。

如果我们想给 [l, r] 的区间加上一个数字 a, 只需要 diff[l] += a,diff[r + 1] -= a。

这样做,diff[0…i] 的和,就是更新后 res[i] 的值。

class Solution {
public:
    int minMoves(vector<int>& nums, int limit) {
        
        // 差分数组, diff[0...x] 的和表示最终互补的数字和为 x,需要的操作数
        // 因为差分数组的计算需要更新 r + 1,所以数组的总大小在 limit * 2 + 1 的基础上再 + 1
        vector<int> diff(limit * 2 + 2, 0);

        int n = nums.size();
        for(int i = 0; i < n / 2; i ++){
            int A = nums[i], B = nums[n - 1 - i];

            // [2, 2 * limit] 范围 + 2
            int l = 2, r = 2 * limit;
            diff[l] += 2, diff[r + 1] -= 2;

            // [1 + min(A, B), limit + max(A, B)] 范围 -1
            l = 1 + min(A, B), r = limit + max(A, B);
            diff[l] += -1, diff[r + 1] -= -1;

            // [A + B] 再 -1    
            l = A + B, r = A + B;
            diff[l] += -1, diff[r + 1] -= -1;
        }

        // 依次求和,得到 最终互补的数字和 i 的时候,需要的操作数 sum
        // 取最小值
        int res = n, sum = 0;
        for(int i = 2; i <= 2 * limit; i ++){
            sum += diff[i];
            if(sum < res) res = sum;
        }
        return res;
    }
};

  • 15
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值