差分是前缀和和的逆过程(类比求导与积分)
- 前缀和可以帮我们通过预处理快速的求出区间的和
- 差分则可以快速帮助我们记录区间的修改
根据场景灵活选择解法:
数组不变,区间查询:前缀和、树状数组、线段树;
数组单点修改,区间查询:树状数组、线段树;
数组区间修改,单点查询:差分、线段树;
数组区间修改,区间查询:线段树
差分数组
核心思想
如果需要将区间[l,r]
内的所有元素的值增加v
,可以拆分为两部分操作:
- 从
nums[l]
开始,其右侧所有的值增加v
实现为差分数组diff[l]+=1
- 从
nums[r+1]
开始,其右侧所有的值减小v
(从而抵消前一步的影响,保证只对[l,r]
区间内的元素产生影响)
实现为差分数组diff[r+1]-=1
- 最终,如果要查询某个点
i
的值改变了多少,从0
到i
累加差分数组的值即可(累计所有区间值得修改对这个点的影响)
应用
LeetCode 1109. 航班预订统计
从 1 到 n 编号的航班,预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位
返回数组,保存每个航班预定的座位总数
如bookings = [[1,2,10],[2,2,15]], n = 2,返回[10,25]
差分数组模板题
class Solution:
def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:
# 暴力做法是对于指定区间内的航班,每个分别叠加游客数量
# 优化:对于每个登记,只在区间开头处叠加游客数
# 最后只需遍历一次各个航班,逐个叠加前一项的乘客数
# 如何保证仅对[i,j]区间叠加游客数k,j+1之后不会多算:在j+1处标记-k,正负抵消即可
cnt = [0 for _ in range(n)] # 航班标号0~n-1,题目中为0~n
for start, end, passengers in bookings:
cnt[start - 1] += passengers
if end < n:
cnt[end] -= passengers
for i in range(1, n):
cnt[i] += cnt[i - 1]
return cnt
LeetCode 798. 得分最高的最小轮调
给你一个数组 nums,可以将它按一个非负整数 k 进行轮调(k=0~len(nums)-1)
每次轮调,会将nums中的前k个元素整体“移动”到nums右侧
每次轮调后,任何值小于或等于其索引的项都可以记一分
给出nums,求得分最高的轮调下标 k,若有多个得分相同的k,返回最小的
思路:
- 当k取值在合适区间内时,一个数字就能贡献得分;k不在合适范围,则这个数字就不能贡献得分
- 因此,我们先遍历一遍数组,对每个数字,考虑当k取何值时该数字能贡献得分,然后在此区间两端标记其贡献,形成差分数组
- 最终i从0到L-1累加差分数组,就可以得到
k=i
时的总得分,维护最大得分即可得到答案
class Solution:
def bestRotation(self, nums: List[int]) -> int:
# 轮换的次数k取值0~L-1,轮换k,数字下标减小k,也可能被移到“右边”,通式为(i_now-k)%L
# 差分数组的思想,当k取值在合适区间内时,一个数字就能贡献得分,我们在区间两端标记其贡献,形成差分数组
L = len(nums)
diff = [0 for _ in range(L)]
for i_now, num in enumerate(nums):
# 对于每个数字,考虑当k取0~L-1中的哪些值,会使num贡献得分
if num <= i_now: # 考虑初始状态就满足条件的数字
# 当前k=0时就开始贡献得分
diff[0] += 1
# 移到num>i时,贡献消失,此时(i_now-k)%L==num-1
if i_now - num + 1 < L:
diff[i_now - num + 1] -= 1
# 再移动,数字被移到“右侧”后,比初始下标还大,从这个k开始到L-1都得分
if i_now + 1 < L:
diff[i_now + 1] += 1
else: # 考虑初始状态不满足条件的数字
# 初始不得分,再左移也不得分
# 只有移到“右边”,才可能开始得分(题目保证num<=L-1,在最有必定得分)
if i_now + 1 < L:
diff[i_now + 1] += 1
# 从最右左移,又可能不满足,此时(L-1-k2)%L==num-1,又移动了k2=L-num次
t = i_now + 1 + L - num
if t < L:
diff[t] -= 1
# k从0~L-1检查,叠加差分数组查询各个k时的得分,找出最大得分
#print(diff)
max_score, max_k = 0, 0
score = 0
for k in range(L):
score += diff[k]
#print(score,k)
if score > max_score:
max_score = score
max_k = k
return max_k