前缀和讲解(一维和二维的情况)

前缀和

1概念

​ 顾名思义,前缀和就是数组当前位置前的所有位置之和,一般用于动态规划的题中,理解前缀和对某些题就可以直接套用模板来做了。

2 初始化

​ 在一维数组中,比如[1, 2, 3, 4]的前缀和树组就是[0, 1, 3, 6, 10];在二维中同理前缀和数组要比给定数组长度大一,这是方便写转移方程。

3 应用场景

​ 在一维数组中,常常用于求数组中索引从i到j的总和,sumRange(i, j)= sum[j + 1] - sum[i],即索引i到j的和等于前j + 1个元素的和减去前i个元素的和,

在这里插入图片描述

​ 二维数组中的前缀和,常常用于求子矩阵范围内元素的总和。

在这里插入图片描述

先求二维的前缀和如下图,来自官方题解:

在这里插入图片描述

然后求sumRegion(row1,col1,row2,col2) = sum[row2 + 1] [col2 + 1] - sum[row1] [col2 + 1] - sum[row2 + 1] [col1] + sum[row1] [col1]

在这里插入图片描述

3 前缀和的一些题目

560. 和为K的子数组
303. 区域和检索 - 数组不可变
304. 二维区域和检索 - 矩阵不可变
363. 矩形区域不超过 K 的最大数值和

4 代码

560.和为K的子数组

class Solution {
    /*
        思路:求出前缀和数组,然后双重遍历该数组,统计k出现的次数
    */
    public int subarraySum(int[] nums, int k) {
        int n = nums.length;
        // 1. 前缀和数组
        int[] f = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            f[i] = f[i - 1] + nums[i - 1];
        }

        // 求出f[j] - f[i] = k的个数,其中j>i,即我们要找到f[i] = f[j] - k出现的次数(注:因为有可能出现负数,因此满足f[i]的个数不止一个)
        // map中存放键表示当前的前缀和,值表示当前前缀和出现的次数。找到满足条件的f[i]后,更新count=count + map.getOrDefault(f[j]-k, 0) + 1
        int count = 0;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);  // 初始化前缀和为0出现的次数为1,因为count初始化是0,因此碰到满足的f[i]时要将count变成1
        for (int i = 1; i <= n; i++) {
            if (map.containsKey(f[i] - k)) {  // 如果map中存在满足条件的键,更新count加上出现的次数
                count += map.get(f[i] - k);
            }
            map.put(f[i], map.getOrDefault(f[i], 0) + 1);
        }
        return count;
    }
}

优化:根据上面的代码我们可以看出来前缀和的计算只与前一项的答案有关,因此可以优化掉f数组。

class Solution {
    public int subarraySum(int[] nums, int k) {
        int n = nums.length;
        int count = 0, pre = 0;

        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);  
        for (int i = 0; i < n; i++) {
            pre += nums[i];
            if (map.containsKey(pre - k)) {  
                count += map.get(pre - k);
            }
            map.put(pre, map.getOrDefault(pre, 0) + 1);
        }
        return count;
    }
}

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

class NumArray {

    private int[] sums;

    public NumArray(int[] nums) {
        int n = nums.length;
        sums = new int[n + 1];
        for (int i = 0; i < n; i++) {
            sums[i + 1] = sums[i] + nums[i];
        }
    }
    
    public int sumRange(int i, int j) {
        return sums[j + 1] - sums[i];
    } 
}

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

class NumMatrix {

    int[][] sums;

    public NumMatrix(int[][] matrix) {
        int m = matrix.length;
        if (m > 0) {
            int n = matrix[0].length;
            sums = new int[m + 1][n + 1];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    sums[i + 1][j + 1] = sums[i + 1][j] + sums[i][j + 1] - sums[i][j] + matrix[i][j];
                }
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2 + 1][col2 + 1] - sums[row1][col2 + 1] - sums[row2 + 1][col1] + sums[row1][col1];
    }
}
  1. 矩形区域不超过 K 的最大数值和
class Solution {
    /*
        思路一:二维前缀和枚举所有情况
        1. 求出所有的前缀和(模板)
        2. 枚举所有情况,即以左上角和右下角为移动点进行全盘扫描,四重循环。
    */
    public int maxSumSubmatrix(int[][] matrix, int k) {
        int m = matrix.length, n = matrix[0].length;
        int[][] sum = new int[m + 1][n + 1];
        // 1. 求前缀和
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }

        int ans = Integer.MIN_VALUE;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                for (int p = i; p <= m; p++) {
                    for (int q = j; q <= n; q++) {
                        int cur = sum[p][q] - sum[i - 1][q] - sum[p][j - 1] + sum[i - 1][j - 1];
                        if (cur <= k) {
                            ans = Math.max(cur, ans);
                        }
                    }
                }
            }
        }
        return ans;
    }
}

优化:二分优化

class Solution {
    public int maxSumSubmatrix(int[][] matrix, int k) {
        int ans = Integer.MIN_VALUE;
        int m = matrix.length, n = matrix[0].length;
        for (int i = 0; i < m; ++i) { // 枚举上边界
            int[] sum = new int[n];
            for (int j = i; j < m; ++j) { // 枚举下边界
                for (int c = 0; c < n; ++c) {
                    sum[c] += matrix[j][c]; // 更新每列的元素和
                }
                TreeSet<Integer> sumSet = new TreeSet<Integer>();
                sumSet.add(0);
                int s = 0;
                for (int v : sum) {
                    s += v;
                    Integer ceil = sumSet.ceiling(s - k);
                    if (ceil != null) {
                        ans = Math.max(ans, s - ceil);
                    }
                    sumSet.add(s);
                }
            }
        }
        return ans;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值