一、前缀和数组与差分数组

五步刷题法

刷题第一遍:花5-10分钟读题+思考,背诵、默写很重要。

刷题第二遍:马上自己写。

刷题第三遍:过了24小时,再重复做题

刷题第四遍:过了一周,反复回来练习相同题目

刷题第五遍:面试前一周或者半个月专门性进行恢复性训练

一、前缀和数组

前言

前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和.
prefix[i+1] 就代表着 nums[0…i] 所有元素的累加和, 如果我们想求区间 nums[i…j] 的累加和, 只要 计算 prefix[j+1] - prefix[i] 即可, ⽽不需要遍历整个区间求和。

在这里插入图片描述

303. 区域和检索 - 数组不可变

在这里插入图片描述
在这里插入图片描述

public class NumArray {
    private int[] preSUm;

    public NumArray(int[] nums) {
        fun(nums);
    }

    //构造前缀和数组
    public void fun(int[] nums) {
        preSUm = new int[nums.length + 1];//preSum[i]代表了nums中前i个数之和,即nums[0...i-1]的和
        //preSum[0]=0为了方便后续计算
        for (int i = 0; i < nums.length; i++) {
            preSUm[i+1] = preSUm[i] + nums[i];
        }
    }

    public int sumRange(int left, int right) {
        return preSUm[right + 1] - preSUm[left];
    }
}

304. 二维区域和检索 - 矩阵不可变

在这里插入图片描述

计算红⾊的这个⼦矩阵的元素之和, 可以⽤绿⾊矩阵减去蓝⾊矩阵减去橙⾊矩阵最后加上粉⾊矩 阵, ⽽绿蓝橙粉这四个矩阵有⼀个共同的特点, 就是左上⻆就是 (0, 0) 原点。

可以维护⼀个⼆维 preSum 数组, 专⻔记录以原点为顶点的矩阵的元素之和, 就可以⽤⼏次加减运 算算出任何⼀个⼦矩阵的元素和:

public class NumMatrix {
    private int[][] preSum;

    public NumMatrix(int[][] matrix) {
        fun(matrix);
    }

    //构造前缀和
    public void fun(int[][] matrix) {
        int n = matrix.length, m = matrix[0].length;
        preSum = new int[n + 1][m + 1];//preSum[i][j]代表matrix[0...i-1][0...j-1]之和
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                preSum[i+1][j+1] = preSum[i][j+1] + preSum[i+1][j] - preSum[i][j] + matrix[i][j];
            }
        }
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        return preSum[row2 + 1][col2 + 1] - preSum[row1][col2 + 1] - preSum[row2 + 1][col1] + preSum[row1][col1];
    }
}

560. 和为 K 的子数组

在这里插入图片描述

//方法一
       public int subarraySum(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int[] preSum = new int[nums.length + 1];//1、构造前缀和
        for (int i = 0; i < nums.length; i++) {
            preSum[i+1] = preSum[i] + nums[i];
        }
        int res = 0;
        for (int i = 0; i < nums.length; i++) {//2、穷举所有元素
            for (int j = 0; j <= i; j++) {
                if (preSum[i+1] - preSum[j] == k) {//3、nums[j-1,...,i]的和
                    res++;
                }
            }
        }
        return res;
    }

在这里插入图片描述
方法2(对方法一的优化)

	public int subarraySum(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        //k--preSum, V--preSum出现的次数
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int res = 0, preSum = 0;
        for (int i = 0; i < nums.length; i++) {
            preSum += nums[i];//记录前缀和
            if (map.containsKey(preSum - k)) {//有满足条件的前缀和
                res += map.get(preSum - k);
            }
            map.put(preSum, map.getOrDefault(preSum, 0) + 1);//更新满足提议的前缀和个数
        }
        return res;
    }

二、差分数组

前言

前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。
差分数组的主要适用场景是频繁对原始数组的某个区间的元素进⾏增减。

比如,输⼊⼀个数组 nums, 然后⼜要求给区间 nums[2…6] 全部加 1 , 再给 nums[3…9] 全部减 3, 再给 nums[0…4] 全部加 2, 再给… 最后 nums 数组的值是什么?

常规的思路很容易,⼀个 for 循环搞定,时间复杂度是 O(N) , 由于这个场景下对 nums 的修改⾮常频繁, 所以效率低下。

这⾥就需要差分数组的技巧, 类似前缀和技巧构造的 preSum 数组, 先对 nums 数组构造⼀个 diff 差
分数组, diff[i] 就是 nums[i] 和 nums[i-1] 之差:

		diff = new int[nums.length];
        diff[0] = nums[0];  // 根据初始数组构造差分数组
        for (int i = 1; i < nums.length; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }

在这里插入图片描述

通过这个 diff 差分数组是可以反推出原始数组 nums 的, 代码逻辑如下:

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

这样构造差分数组 diff, 就可以快速进⾏区间增减的操作, 如果你想对区间 nums[i…j] 的元素全部加 3, 那么只需要让 diff[i] += 3, 然后再让 diff[j+1] -= 3 即可:在这里插入图片描述
diff[i] += 3 意味着给 nums[i…] 所有的元素都 加了 3, 然后 diff[j+1] -= 3 ⼜意味着对于 nums[j+1…] 所有元素再减 3, 那综合起来, 便是对 nums[i…j] 中的所有元素都加 3。

只要花费 O(1 ) 的时间修改 diff 数组, 就相当于给 nums 的整个区间做了修改。 多次修改 diff, 然后通过diff 数组反推, 即可得到 nums 修改后的结果。

差分数组模板

class Difference {
    private int[] diff;// 差分数组

    public Difference(int[] nums) {/* 输⼊⼀个初始数组, 区间操作将在这个数组上进⾏ */
        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;
        if (j + 1 < diff.length) {
            diff[j + 1] -= val;
        }
    }

    public int[] result() {/* 返回结果数组 */
        int[] res = new int[diff.length];
        res[0] = diff[0];// 根据差分数组构造结果数组
         /*当 j+1 >= diff.length 时, 说明是对 nums[i] 及以后的整个数组都进⾏修改,
         那么就不需要再给 diff 数组减 val 了。*/
        for (int i = 1; i < diff.length; i++) {
            res[i] = diff[i] + res[i - 1];
        }
        return res;
    }
}

370.区间加法

在这里插入图片描述

int[] getModifiedArray(int length, int[][] updates) {
        int nums[] = new int[length];// nums 初始化为全 0
        Difference df = new Difference(nums);// 构造差分解法
        for (int[] update : updates) {
            df.increment(update[0], update[1], update[2]);
        }
        return df.result();
    }

1109.航班预定统计

在这里插入图片描述

public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] nums = new int[n];// nums 初始化为全 0
        Difference df = new Difference(nums);// 构造差分解法
        for (int[] booking : bookings) {
            int i = booking[0] - 1;// 注意转成数组索引要减⼀
            int j = booking[1] - 1;
            int val = booking[2];
            df.increment(i, j, val);// 对区间 nums[i..j] 增加 val
        }
        return df.result();// 返回最终的结果数组
    }

1094.拼车

在这里插入图片描述

public boolean carPooling(int[][] trips, int capacity) {
        int[] nums = new int[1001];// 最多有 1000 个⻋站
        Difference df = new Difference(nums);// 构造差分解法
        for (int[] trip : trips) {
            int i = trip[1];// 第 trip[1] 站乘客上⻋
            // 第 trip[2] 站乘客已经下⻋,即乘客在⻋上的区间是 [trip[1], trip[2] - 1]
            int j = trip[2] - 1;
            int val = trip[0];// 乘客数量
            df.increment(i, j, val);// 进⾏区间操作
        }
        int[] res = df.result();
        for (int i = 0; i < res.length; i++) {// 客⻋⾃始⾄终都不应该超载
            if (res[i] > capacity) {
                return false;
            }
        }
        return true;
    }

}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值