Leetcode——区域和检索 - 数组不可变 / 二维区域和检索 - 矩阵不可变 (前缀和)

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

在这里插入图片描述

(1)前缀和

由于涉及 -1 操作,为了减少一些边界处理,我们可以使前缀和数组下标从 1 开始记录,然后在进行答案计算的时候,根据源数组下标是否从 1 开始决定是否产生相应的偏移:

下标从1开始:

class NumArray {
    int[] sum;
    public NumArray(int[] nums) {
        int n = nums.length;
        //前缀和数组下标从 1 开始,因此设定长度为 n + 1(模板部分)
        sum = new int[n + 1];
        //预处理除前缀和数组(模板部分)
        for (int i = 1; i <= n; i++) 
        	sum[i] = sum[i - 1] + nums[i - 1];
    }

    //sum数组中存的是前缀和,比如nums = [1,2,3,4]  输出:[1,3,6,10],但是注意sum的下标是从1开始
    public int sumRange(int i, int j) {
        // 求某一段区域和 [i, j] 的模板是 sum[j] - sum[i - 1](模板部分)
        // 但由于我们源数组下标从 0 开始,因此要在模板的基础上进行 + 1
        i++; 
        j++;
        return sum[j] - sum[i - 1];         //减1是为了包含i,j两点(主要是i)
    }
}

下标也可以从0开始:

class NumArray {
    int[] sum;
    int[] nums;
    public NumArray(int[] nums) {
        int n = nums.length;
        //前缀和数组下标从 1 开始,因此设定长度为 n + 1(模板部分)
        this.nums = nums;
        sum = new int[n];
        sum[0] = nums[0];
        for (int i = 1; i < n; i++) 
        	sum[i] = sum[i - 1] + nums[i];
    }

    //sum数组中存的是前缀和,比如nums = [1,2,3,4]  输出:[1,3,6,10]
    //下标从1开始
    public int sumRange(int i, int j) {
        return sum[j] - sum[i] + nums[i];         
    }
}

(2)一维前缀和模板

下标从1开始:

// 预处理前缀和数组
{    
    sum = new int[n + 1];
    for (int i = 1; i <= n; i++) 
    	sum[i] = sum[i - 1] + nums[i - 1];
}

// 计算 [i, j] 结果
{
    i++; 
    j++;
    ans = sum[j] - sum[i - 1];
}

下标从0开始:

// 预处理前缀和数组
int[] nums;
{    
	this.nums = nums;
    sum = new int[n];
    for (int i = 1; i < n; i++) 
    	sum[i] = sum[i - 1] + nums[i];
}

// 计算 [i, j] 结果
{
    ans = sum[j] - sum[i] + nums[i];
}

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

在这里插入图片描述

(1)一维 前缀和

在这里插入图片描述
对二维矩阵,求子矩阵 (n*m) 的和。暴力法就是两重循环,累加求和。

每次查询花费 O(n*m) 时间,n和m是子矩阵的行数和列数。查询的代价有点大。

class NumMatrix {
    int[][] sums;

    //创建 m 行n+1 列的二维数组 sums,其中 m 和 n 分别是矩阵matrix 的行数和列数
    //sums[i] 为 matrix[i] 的前缀和数组。将sums 的列数设为 n+1 的目的是为了方便计算每一行的子数组和,不需要col=0 的情况特殊处理

    public NumMatrix(int[][] matrix) {
        int m = matrix.length;
        if (m > 0) {
            int n = matrix[0].length;
            sums = new int[m][n + 1];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    sums[i][j + 1] = sums[i][j] + matrix[i][j];
                }
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        int sum = 0;
        for (int i = row1; i <= row2; i++) {
            sum += sums[i][col2 + 1] - sums[i][col1];
        }
        return sum;
    }
}

(2)优化

  • 上面的暴力法其实也分了 n 步:第一行的求和,到第 n 行的求和
  • 它们分别是 n 个一维数组。
  • 我们学习了一维前缀和,我们可以对这n个一维数组求前缀和,得到n个一维preSum数组。
  • 为了节省查询的时间,我们求出整个矩阵每一行的一维preSum数组
  • 根据定义 preSum[i] = nums[0] + nums[1] +…+nums[i]preSum[i]=nums[0]+nums[1]+…+nums[i] ,求出下图红字部分:
    在这里插入图片描述
    然后套用通式:nums[i]+…+nums[j] = preSum[j]−preSum[i−1]
    即可求出粉色子阵列的和,计算情况如下图。

在这里插入图片描述
可见,如果想多次查询子阵列的和,我们可以提前求出每一行数组的一维前缀和。

那么查询阶段,求出一行子数组的求和,就只是 O(1)O(1)

查询 n 行的子阵列,每次就查询花费 O(n) 比 O(n2) 好

class NumMatrix {
    int[][] A;
    int row;
    int column;
    int[][] presum;
    boolean flag=true;
    
    public NumMatrix(int[][] matrix) {
        A = matrix;
        row = matrix.length;
        if (row != 0) 
            column = matrix[0].length;
        else 
            flag = false;
        presum = new int[row][column+1];
        for (int i = 0; i < row; i++) {
        	for (int j = 0; j < column; j++) {
        		presum[i][j+1]=presum[i][j]+A[i][j];
        	}
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        if (flag == false) 
            return 0;
    	int sum = 0;
    	for (int i = row1; i <= row2; i++) {
    		sum += presum[i][col2+1] - presum[i][col1];
    	}
    	return sum;
    }
}

(3)继续优化

未完待续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yawn__

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

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

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

打赏作者

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

抵扣说明:

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

余额充值