算法学习笔记——常用技巧:差分数组

差分是前缀和和的逆过程(类比求导与积分)

  • 前缀和可以帮我们通过预处理快速的求出区间的和
  • 差分则可以快速帮助我们记录区间的修改

根据场景灵活选择解法:
数组不变,区间查询:前缀和、树状数组、线段树;
数组单点修改,区间查询:树状数组、线段树;
数组区间修改,单点查询:差分、线段树;
数组区间修改,区间查询:线段树

差分数组

核心思想

如果需要将区间[l,r]内的所有元素的值增加v,可以拆分为两部分操作:

  1. nums[l]开始,其右侧所有的值增加v
    实现为差分数组diff[l]+=1
  2. nums[r+1]开始,其右侧所有的值减小v(从而抵消前一步的影响,保证只对[l,r]区间内的元素产生影响)
    实现为差分数组diff[r+1]-=1
  3. 最终,如果要查询某个点i的值改变了多少,从0i累加差分数组的值即可(累计所有区间值得修改对这个点的影响)

应用

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值