力扣刷题记录-差分数组

差分数组的思想和前缀和数组类似,主要适用场景是频繁对原始数组的某个区间的元素进行增减。举个例子就是:给一个数组nums,要求对nums一些区间进行若干次区间内元素的全部加或者减,比nums=[3,2,4,5,8,2,4,6,8,7],要求对下标1-5区间内元素全部加4,再对下标3-9区间内元素全部+3,……,执行若干次这样的操作后,问最终的nums数组元素是什么样的?

很容易想到对每一次操作都执行一次for循环给元素赋值,但是如果这样的操作多了起来之后,每次时间复杂度都是O(n),效率低。这时候就可以构造一个差分数组优化时间了。

可以构造一个差分数组diff,diff[i] 就是 nums[i] 和 nums[i-1] 之差:

int[] diff = new int[nums.length];
// 构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
    diff[i] = nums[i] - nums[i - 1];
}
nums41972
diff4-38-2-5

通过diff数组,可以很容易推回nums的值:nums[i]=nums[i-1]+diff[i];

有了diff数组,对一个区间的所有元素进行加或减就很方便了:想对nums下标i到j区间内元素(包括i和j)全部加5,只要让diff[i]+=5,再让diff[j+1]-=5就行,比如i=1,j=3如下:

下标01(i)23(j)4
nums4614122
diff4-3+5=28-2-5-5=-10

让diff[i]+=5,相当于让nums[i]开始的元素都加5,再让diff[j+1]-=5,相当于从nums[j+1]开始的元素保持原来大小,这样就相当于对i到j区间元素都加5.

这样每次只要O(1)时间对diff数组修改,就可以完成nums的区间加减,代码如下:

// 差分数组工具类(代码来自于labuladong公众号)
class Difference {
    // 差分数组
    private int[] diff;
    
    /* 输入一个初始数组,区间操作将在这个数组上进行 */
    public Difference(int[] nums) {
        assert nums.length > 0;
        diff = new int[nums.length];
        // 根据初始数组构造差分数组
        diff[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
    }

    /* 给闭区间 [i, j] 增加 val(可以是负数)*/
    public void increment(int i, int j, int val) {
        diff[i] += val;
        //当 j+1 >= diff.length 时,说明是对 nums[i] 及以后的整个数组都进行修改,那么就不需要再给 diff 数组减 val 了
        if (j + 1 < diff.length) {
            diff[j + 1] -= val;
        }
    }

    /* 返回结果数组 */
    public int[] result() {
        int[] res = new int[diff.length];
        // 根据差分数组构造结果数组
        res[0] = diff[0];
        for (int i = 1; i < diff.length; i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
}

力扣 370. 区间加法

原题链接
在这里插入图片描述

//差分数组,空间O(n),每次操作时间O(1),进行k次,最后生成res时间O(n)
//总时间O(k+n)
class Solution {
    public int[] getModifiedArray(int length, int[][] updates) {
        //差分数组,原数组初始为0,则差分数组初始也是全0
        int[] diff=new int[length];
        for(int[] update:updates){
            int i=update[0];//索引下标区间[i,j]
            int j=update[1];
            int m=update[2];//增加的值
            diff[i]+=m;
            // j+1 >= length 时,说明是对 nums[i] 及以后的整个数组都进行修改,那么就不需要再给 diff 数组减回去了。
            if(j+1<length)
                diff[j+1]-=m;
        }
        //结果数组
        int[] res=new int[length];
        //根据差分数组复原res
        res[0]=diff[0];
        for(int i=1;i<length;i++){
            res[i]=res[i-1]+diff[i];
        }
        return res;
    }
}

力扣 1109. 航班预订统计

原题链接
在这里插入图片描述
这道题目翻译一下就是给你一个长度为n的answer数组,初始全为0。再给一个bookings三元组数组,bookings每一个三元组都是[i,j,k],表示让你把nums下标[i-1,j-1]范围内的元素全部加上k(因为数组下标从0开始,航班编号从1开始)。这就是典型的差分数组题。

直接上代码:

//差分数组,时间O(n+k),answer赋值的n,以及k条记录,空间O(1)(如果结果数组不计入空间)
class Solution {
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] diff=new int[n];//差分数组初始为0
        for(int[] booking:bookings){
            diff[booking[0]-1]+=booking[2];
            if(booking[1]<n)
                diff[booking[1]]-=booking[2];
        }
        //结果数组
        int[] answer=new int[n];
        answer[0]=diff[0];
        for(int i=1;i<n;i++){
            answer[i]=answer[i-1]+diff[i];
        }
        return answer;
    }
}

其实只需要用一个数组即可:

class Solution {
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] diff=new int[n];//差分数组初始为0
        for(int[] booking:bookings){
            diff[booking[0]-1]+=booking[2];
            if(booking[1]<n)
                diff[booking[1]]-=booking[2];
        }
        //可以不用新建结果数组,是因为题目中原航班量全为0,所以直接用差分数组开始构造即可
        for(int i=1;i<n;i++){
            diff[i]+=diff[i-1];
        }
        return diff;
    }
}


力扣 1094. 拼车

原题链接

在这里插入图片描述

这题其实是把所有可能的上下车的位置抽象成一个站点数组,数组大小为1001,下标从0到1000;在若干个站点区间内进行区间元素加法(乘客在车上时),乘客下车把加上的元素复原,最后判断每个站点数组元素值是否超过capacity即可,很典型的差分数组类型题:

//差分数组
class Solution {
    public boolean carPooling(int[][] trips, int capacity) {
        //0到1000,最多有1001个站
        int[] diff=new int[1001];
        
        //差分数组赋值
        for(int[] trip:trips){
        	//乘客在trip[1]处上车
            diff[trip[1]]+=trip[0];
            //trip[2]这个站点乘客下车,我们要计算的是乘客在车上的站点范围[trip[1],trip[2]-1]
            //所以从trip[2]减回去
            if(trip[2]<1001)//这题里面可以省略,因为题目规定了下车站点最大为1000
                diff[trip[2]]-=trip[0];
        }

        int res=0;
        //res记录车上乘客数量
        for(int i=0;i<diff.length;i++){
            res+=diff[i];
            if(res>capacity)return false;
        }
        return true;//遍历完都符合,说明可以
    }
}

力扣 253. 会议室 II

原题链接
在这里插入图片描述
这题其实用差分数组效率很低,但是更高效的解法也是建立在差分数组思想上的,所以可以先尝试下差分。

其实就是把所有的会议室的时间抽象成大小为106+1大小的数组,每个使用会议室的时间段ntervals[i] = [starti, endi],就代表这个时间段内会议室的数量需要+1,这个时间段结束后再-1恢复。最后遍历一遍差分数组,比较每个时间点的会议室数量,最大的那个数就是要求的会议室数量。

//差分数组(时空效率很差)
class Solution {
    public int minMeetingRooms(int[][] intervals) {
        int len=0;
        //找到最大的时间
        for(int[] interval:intervals){
            len=Math.max(len,interval[1]);
        }
        int[] diff=new int[len];
        //构建差分数组
        for(int[] interval:intervals){
            diff[interval[0]]+=1;
            if(interval[1]<len)
                diff[interval[1]]-=1;
        }
        int num=0,res=0;//num记录每个时间点的会议室数量。res记录最大的num
        for(int i=0;i<diff.length;i++){
            num+=diff[i];
            res=Math.max(res,num);
        }
        return res;
    }
}

当然这题有更好的解法,假想将每个使用时间的区间起点和终点都投影到一个线上,区分好每一个区间起点和区间终点,设置一个count计数器记录会议室数量。用一个指针扫描这些起点和终点,只要碰到一个起点,说明有一个会议马上开始,count就要+1,遇到一个终点,说明有一个会议结束,count就-1,这样每个时刻有多少会议就是count的大小。这个扫描过程就是差分数组生成res数组的过程。

//双指针扫描,时间O(nlogn)(排序算法的耗时),空间O(n)
class Solution {
    int minMeetingRooms(int[][] meetings) {
        int n = meetings.length;
        int[] begin = new int[n];
        int[] end = new int[n];
        for(int i = 0; i < n; i++) {
            begin[i] = meetings[i][0];
            end[i] = meetings[i][1];
        }
        Arrays.sort(begin);//将区间起点和终点分别排序
        Arrays.sort(end);

        // 扫描过程中的计数器
        int count = 0;
        // 双指针技巧
        int res = 0, i = 0, j = 0;
        while (i < n && j < n) {
            if (begin[i] < end[j]) {
                // 扫描到一个起点
                count++;
                i++;
            } else {
                // 扫描到一个区间终点
                count--;
                j++;
            }
            res = Math.max(res, count);// 记录扫描过程中的最大值
        }
        return res;
    }
}

AcWing 798. 差分矩阵

原题链接
在这里插入图片描述

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;

public class Main{
    static int N=1010;
    
    //对二维差分矩阵进行操作,O(1)
    public static void insert(int[][] diff,int x1,int y1,int x2,int y2,int c){
        diff[x1][y1]+=c;
        diff[x2+1][y1]-=c;
        diff[x1][y2+1]-=c;
        diff[x2+1][y2+1]+=c;
    }
    public static void main(String[] args)throws IOException{
        //Scanner会超时,所以使用BufferedReader,更快
        BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(System.out));
        //将输入的一行字符按去除空格的方式分割进入字符串数组
        String[] str1=reader.readLine().split(" ");
        int n=Integer.parseInt(str1[0]);//需要将字符转为int类型,可以调用Integer包装类的方法
        int m=Integer.parseInt(str1[1]);
        int q=Integer.parseInt(str1[2]);
        
        int[][] nums=new int[N][N];//nums矩阵为原矩阵
        int[][] diff=new int[N][N];//差分矩阵前缀和就是nums矩阵
        
        //初始化原矩阵与差分矩阵
        for(int i=1;i<=n;i++){
            //二维矩阵的按行读入str2,下标从0开始
            String[] str2=reader.readLine().split(" ");
            for(int j=1;j<=m;j++){
                nums[i][j]=Integer.parseInt(str2[j-1]);//str2的j从0开始
                //初始化二维差分数组(等效于在diff的(i,j)处加上nums[i][j])
                insert(diff,i,j,i,j,nums[i][j]);
            }
        }
        
        //
        while(q-->0){
            String[] str3=reader.readLine().split(" ");
            int x1=Integer.parseInt(str3[0]);
            int y1 = Integer.parseInt(str3[1]);
            int x2 = Integer.parseInt(str3[2]);
            int y2 = Integer.parseInt(str3[3]);
            int c  =  Integer.parseInt(str3[4]);
            insert(diff,x1,y1,x2,y2,c);
        }
        
        //求二维差分数组的前缀和,就是所求的最终矩阵(原理和二维前缀和数组相同)
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                diff[i][j]+=diff[i-1][j]+diff[i][j-1]-diff[i-1][j-1];
                writer.write(diff[i][j]+" ");
            }
            writer.write("\n");
        }
        
        //所有write下的内容,会先存在writers中,当启用flush以后,会输出存在其中的内容。
        //如果没有调用flush,则不会将writer中的内容进行输出。
        writer.flush();
        reader.close();
        writer.close();
    }
}




  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于力扣刷C++常用操作,我可以给你一些常见的操作和技巧: 1. 使用 STL 容器和算法库:STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,包含了许多常用的容器和算法。在力扣刷中,使用 STL 可以大大提高代码的效率和可读性。例如,vector 可以用来存储动态数组,sort 可以用来排序等等。 2. 使用 auto 关键字:auto 关键字可以自动推导变量类型,可以减少代码量和提高可读性。例如,auto x = 1; 可以自动推导出 x 的类型为 int。 3. 使用 lambda 表达式:lambda 表达式是 C++11 中引入的一种匿名函数,可以方便地定义一些简单的函数对象。在力扣刷中,使用 lambda 表达式可以简化代码,例如在 sort 函数中自定义比较函数。 4. 使用位运算:位运算是一种高效的运算方式,在力扣刷中经常会用到。例如,左移运算符 << 可以用来计算 2 的幂次方,右移运算符 >> 可以用来除以 2 等等。 5. 使用递归:递归是一种常见的算法思想,在力扣刷中也经常会用到。例如,二叉树的遍历、链表的反转等等。 6. 使用 STL 中的 priority_queue:priority_queue 是 STL 中的一个容器,可以用来实现堆。在力扣刷中,使用 priority_queue 可以方便地实现一些需要维护最大值或最小值的算法。 7. 使用 STL 中的 unordered_map:unordered_map 是 STL 中的一个容器,可以用来实现哈希表。在力扣刷中,使用 unordered_map 可以方便地实现一些需要快速查找和插入的算法。 8. 使用 STL 中的 string:string 是 STL 中的一个容器,可以用来存储字符串。在力扣刷中,使用 string 可以方便地处理字符串相关的问。 9. 注意边界条件:在力扣刷中,边界条件往往是解决问的关键。需要仔细分析目,考虑各种边界情况,避免出现错误。 10. 注意时间复杂度:在力扣刷中,时间复杂度往往是评判代码优劣的重要指标。需要仔细分析算法的时间复杂度,并尽可能优化代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值