【算法】差分数组-基础篇

差分数组

本质上说,就是一个数组,但可以在O(1)的时间复杂度处理区间修改;

形式

假设有一数组a,差分数组为d,有:任一 i ∈ [ 2 , n ] i \in[2,n] i[2,n],都有 d [ i ] = a [ i ] − a [ i − 1 ] d[i] = a[i]-a[i-1] d[i]=a[i]a[i1],则可以发现有以下性质:原数组第a[n]项为: a [ n ] = ∑ i = 0 n d [ i ] a[n]=\sum_{i=0}^{n}d[i] a[n]=i=0nd[i],原数组前n项和Sum为: S u m ( n ) = ∑ i = 0 n a [ i ] = ∑ i = 0 n ∑ i = 0 n d [ i ] = ∑ i = 0 n ( n − i + 1 ) d [ i ] Sum(n) = \sum_{i=0}^{n}a[i]=\sum_{i=0}^{n}\sum_{i=0}^{n}d[i]=\sum_{i=0}^{n}(n-i+1)d[i] Sum(n)=i=0na[i]=i=0ni=0nd[i]=i=0n(ni+1)d[i],

用法

主要支持两种操作:1.区间修改 2.单点查询

  • 快速处理区间加减操作:对数列区间[L,R]中的每个数都加上x,根据性质一,只需要将查分数组中的d[L]+x,而a[R+1]后的数没有加上x,则需要将d[R+1]-x,这样对后面的数就没有影响;
  • 询问区间和问题:根据性质二我们知道前缀和Sum,区间和[L,R]的和为:Sum® -Sum(L);

举例

原数组:3,6,8,7,4,3,2,9 ;查分数组:3,3,2,-1,-3,-1,-1,7

区间加减:比如对下标[3,6]之间的数字都加上2,原数组变为:3,6,8,9,6,5,4,9。而查分数组变为:3,3,2,1,-3,-1,-1,5

力扣题目(LeetCode)

1.1109.航班预定统计
2.798. 得分最高的最小轮调
3. 1094. 拼车

mycode and analysis

1109.航班预定统计

   //简单思路版本,执行耗时:799 ms,击败了15.08% 的Java用户,
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] res  = new int[n];
        int size = bookings.length;
        for (int i = 0; i < size; i++) {
            int l = bookings[i][0],r= bookings[i][1],seats= bookings[i][2];
            for (int j = l; j <= r; j++) {
                res[j-1]+=seats;
            }
        }
        return res;
    }

//差分数组版本:执行耗时:2 ms,击败了100.00% 的Java用户
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] d = new int[n];
        int[] res=new int[n];
        int size = bookings.length;
        for (int i = 0; i < size; i++) {
            int l = bookings[i][0],r = bookings[i][1],seats =bookings[i][2];
            d[l-1]+=seats;
            if(r<n)d[r]-=seats;
        }
        res[0]=d[0];
        for (int i = 1; i < n; i++) {
            res[i]=res[i-1]+d[i];
        }
        return res;
    }

[798. 得分最高的最小轮调]

 public int bestRotation(int[] nums) { //执行耗时:6 ms,击败了84.56% 的Java用户
        //思路在于:我们可以找到nums中每个数 轮转得分的范围,然后再范围内都加一;
        // 范围内加上一个固定的数,便可以想到使用 差分数组;
        int res,n=nums.length;
        int[] points = new int[n];

        for (int i = 0; i < n; i++) {
            //计算每个数 能得分的k 的范围
            // 比如100个数,第5个数为34,能得分的下标范围为[34,99],那么移动的k值应该为[6,66]
            // 比如 ,第26个数为5,能得分的下标范围为:[5,99],移动k值的范围为:[0,21]\[27,99];
            // 那么一般情况下:nums[i] 能得分的范围为:[0,i],移动次数的范围要分类讨论
            if(nums[i]>i){
                //k值取值范围应该为:
                int l =i+1,r=(n-nums[i]+i)%n;
                points[l]+=1;
                if(r+1 <n) points[r+1]-=1;
            }else if(nums[i]<=i) { //两个区间
                points[0]+=1;
                int r = i-nums[i];
                if(r+1<n) {
                    points[r + 1] -= 1;
                    //另外一个区间为[n-nums[i]+i,n-1]
                    if (i + 1 < n) points[i + 1] += 1;
                }
            }
        }

//遍历获取最小的k值;
        res= points[0];
        int max = points[0];
        int index=0;
        for (int i = 1; i < n; i++) {
            res+=points[i];
            if(res>max){
                max=res;
                index = i;
            }else if(res==max){
                if(index >i) index=i;
            }
        }
        return index;

    }

1094. 拼车

    public boolean carPooling(int[][] trips, int capacity) {
        int[] station = new int[1000+1]; //差分数组
        int maxMile =0; //记录最远距离;
        int n = trips.length;
        for (int i = 0; i < n; i++) {  
            int addPersons = trips[i][0]; //跟上面几题一样;
            if(maxMile<trips[i][2]) maxMile = trips[i][2];
            station[trips[i][1]]+=addPersons;
            station[trips[i][2]]-=addPersons;
        }

        int personCar =0;
        for (int i = 0; i <= maxMile; i++) {
            personCar +=station[i];
            if(personCar>capacity) return false;
        }
        return true;
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

格局不能小

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值