逆天赘婿逆袭之他要学习——《差分》

一、差分定义

对于一个数组a[],其差分数组diff[]的表示为:diff[i] = a[i] - a[i-1],其中,i从1开始,diff[1] = a[1],数组的下标也从1开始。

从这个定义中,我们可以得到差分数组的一个性质:对差分数组求前缀和,可以还原原数组,这里我们用一个简单的过程来描述。

diff[1] + diff[2]+...+diff[i]

=a[1] + (a[2]-a[1])+...+(a[i]-a[i-1])

=a[i]

二、常规对于区间修改问题的处理

假定我们有一个数组a[],我们现在的任务是将其[l,r]的区间上所有数都加上值C,并输出这个数组,常规的做法是:

  1. 遍历数组区间

  2. 将所有的数值都加C

  3. 输出数组

如果我们只对它的这一个区间进行操作,这样做没有什么问题,但是如果对数组a[],我们不只要进行从区间[l,r]上的操作,还要在区间[p,q],[m,n]...好多个区间上进行操作,我们要怎么办?

  1. 遍历第一个区间

  2. 将所有的数值都进行操作

  3. 遍历第二个区间

  4. 将所有的数值都进行操作

  5. ....(重复操作)

  6. 输出数组

可见这个过程是十分繁琐的,假如我们有特别多的区间,运行时间会过长,这个时候我们就要运用差分数组了。

那么我们应该怎么运用差分数组呢?我们首先思考,我想对区间[l,r]进行加上一个数值C,那么我的差分数组应该怎么办呢?

三、差分数组的区间操作

我们希望通过差分数组来优化区间加法操作。那么,当我们想对区间[l, r]上的所有元素加上一个值C时,应该如何修改差分数组呢?

我们回顾一下差分数组的定义:diff[i] = a[i] - a[i-1]。这个定义告诉我们,差分数组的每一个元素代表了原数组中相邻元素的差值。那么,当我们想对区间[l, r]进行加C操作时,我们只需要在差分数组中做两步修改:

  1. 在索引l的位置增加C:这是因为我们希望从位置l开始,原数组的所有元素都增加C,这会在差分数组中反映为diff[l] += C。例如我们a[l]是5,a[l-1]是3,我们现在让a[l]加上一个C,原本的diff[l]=a[l]-a[l-1]=3,而现在就是diff[l]=a[l]-a[l-1]=5+C-3=2+C,也就有了diff[l] += C

  2. 在索引r+1的位置减少C:我们希望加C的操作只在区间[l, r]内生效,所以我们需要在r+1的位置将差分值减去C,这样从r+1开始,后续的元素就不会再受此操作影响。具体操作为diff[r+1] -= C。分析过程和索引l处一致。

  3. 中间部分:中间部分当然是不变的啦。

通过这种方式,我们在差分数组中只做了两次修改,而不需要遍历区间内的每个元素,这大大提高了效率。

举个栗子:

假设原数组 a[] 是这样的:

a[] = [1, 2, 3, 4, 5]

我们希望进行以下几个区间操作:

  1. 对区间 [2, 4] 中的元素加上 C = 3

  2. 对区间 [1, 3] 中的元素加上 C = 2

  3. 对区间 [4, 5] 中的元素加上 C = 1


首先,我们根据原数组计算出差分数组 diff[]

a[] = [1, 2, 3, 4, 5]
diff[] = [1, 1, 1, 1, 1]

对区间 [2, 4] 加上 C = 3

为了对区间 [2, 4] 中的元素加上 3,我们对差分数组进行如下修改:

  • diff[2] += 3(在索引 2 开始加 3)

  • diff[5] -= 3(在索引 5 结束,索引 5 之后不再加 3)

更新后的差分数组:

diff[] = [1, 4, 1, 1, -3]

对区间 [1, 3] 加上 C = 2

接下来,对区间 [1, 3] 中的元素加上 2,我们对差分数组进行如下修改:

  • diff[1] += 2(在索引 1 开始加 2)

  • diff[4] -= 2(在索引 4 结束,索引 4 之后不再加 2)

更新后的差分数组:

diff[] = [3, 4, 1, -1, -3]

对区间 [4, 5] 加上 C = 1

最后,对区间 [4, 5] 中的元素加上 1,我们对差分数组进行如下修改:

  • diff[4] += 1(在索引 4 开始加 1)

  • diff[6] -= 1(在索引 6 结束,索引 6 之后不再加 1)

更新后的差分数组:

diff[] = [3, 4, 1, 0, -3, -1]

还原原数组

现在,我们可以通过前缀和计算来还原更新后的原数组:

  • a[1] = diff[1] = 3

  • a[2] = a[1] + diff[2] = 3 + 4 = 7

  • a[3] = a[2] + diff[3] = 7 + 1 = 8

  • a[4] = a[3] + diff[4] = 8 + 0 = 8

  • a[5] = a[4] + diff[5] = 8 + (-3) = 5

  • a[6] = a[5] + diff[6] = 5 + (-1) = 4(其实这个元素不存在)

所以,更新后的数组 a[] 为:

a[] = [3, 7, 8, 8, 5]

所以,通过差分数组,我们成功地在多个区间上执行了加法操作,并且每次操作只需要在差分数组中进行两次修改。最终,通过一次前缀和计算,我们还原了更新后的数组,且时间复杂度得到了显著优化。

四、实现代码

#获取差分数组
def get_diff(a):
    n = len(a)
    diff = [0] * (n + 1)
    diff[0] = a[0]
    for i in range(1, n):
        diff[i] = a[i] - a[i - 1]
    return diff
​
#实现对l~r的区间进行加上或减去一个数
def change_diff(l, r, z, diff):
    # 需要注意,x,y 给的是第几个元素,并非下标
    diff[l - 1] = diff[l - 1] + z
    diff[r] = diff[r] - z # diff数组多加上一位是为了避免这里不越界
    return diff

在这里需要注意的是,在创建diff数组的时候采取多加了一维,这是为了保证右边界不会越界。

五、例题

具体在什么时候会用到差分?笔者总结了这两点:

  • 对一个数组的某个连续区间的元素统一进行操作

  • 这样的连续区间有多个

1、重新排序

点此前往“重新排序”

2、区间更新

点此前往“区间更新”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值