力扣日常打卡---304.二位区域和检索

题目描述:
给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。
在这里插入图片描述
上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。
示例:
在这里插入图片描述
说明:

  1. 你可以假设矩阵不可变。
  2. 会多次调用 sumRegion 方法。
  3. 你可以假设 row1 ≤ row2 且 col1 ≤ col2。

思路与题解:
思路1:暴力法
即列举左上角至右下角所有的元素,把他们相加,这样付出的代价为:时间复杂度O(mn),空间复杂度O(1); 其中m和n分别为原矩阵的行数与列数。但是,题目中提到会多次调用计算区域和函数,这样的时间复杂度显然是不行的,那么我们只能考虑用空间去换时间。(暴力法代码略)

思路二:前缀和
那么,怎么取用空间换时间呢?可以发现,如果连续两次的求和区域非常接近,比如(0,0)-(2,2)和(0,0)-(2,3),如果用暴力法去求解的话,前面求出来的(0,0)-(2,2)的结果没有很好的利用起来,导致在后一次计算是进行了重复的计算。

所以我们考虑把一部分计算出来的值存起来。下一步问题是怎么存,如果把所有可能区域的和都提前计算一遍,确实可以达到查找为O(1)的效果,但是如果原始矩阵特别大,这样的枚举左上角坐标与右下角坐标的做法是不可取的。

由此我们想到:固定一个顶点!
我们此处选择固定左上角顶点(当然固定右下脚顶点也行得通,请各位试一试)。

我们用一个大小和原矩阵相同的矩阵temp去预存放一些值,这些值表示:
设i,j分别为该矩阵的第i行和第j列;
那么temp[i][j]表示的是以左上角为起始点,到右下角为(i,j)这个区域里面的数的和。

那么还是同样的问题,怎么样简便的计算这些值呢而不是用暴力法去枚举呢?我们看一个简单的例子:

在这里插入图片描述
若此矩阵只有一行(图中蓝色框所示)
temp[i]表示的是前 i 个数的和,那么:

i=0时,temp[0] = arr[0];
i=1时,temp[1] = arr[0]+arr[1]=temp[0]+arr[1];
i=2时,temp[2] = arr[0]+arr[1]+arr[2] = temp[1]+arr[2];
… …
从上可知递推式为:

  1. temp[0] = arr[0];
  2. temp[i] = temp[i-1]+arr[i](i>0)

同理,对于第一列也是如此。

接下来,我们来看二维的情况下怎么简便的去求取temp[i][j]的值:
在这里插入图片描述
如上图所示,若要求取蓝色框区域的值,由一维数组的推论,肯定和前面所求取的值有关。
我们不难发现:在求取(2,3)为右下角坐标的区域值之前,根据遍历的顺序,肯定以(2,2)为右下角的区域(下图绿框)值已经计算出来了,只剩下三个值没有加入到和之中了,但是temp[1][3]又表示的是(0,0)为左上角,(1,3)为右下角的区域(下图黄框)和,直接加上去的话,我们会发现多加了一个区域(下图红框),那就是(0,0)-(1,2),所以只需减去这个区域的值就行了那就是对应的temp[1][2],最后再加上右下角的值就行了。

综上,递推式如下:

  1. temp[0][0] = arr[0][0];
  2. temp[i][j] = temp[i][j-1]+arr[i][j],i=0;
  3. temp[i][j] = temp[i-1][j]+arr[i][j], j=0;
  4. temp[i][j] = temp[i-1][j]+temp[i][j-1]-temp[i-1][j-1]+arr[i][j], i>0&&j>0;
    在这里插入图片描述
    至此,工作已经做了一大半了,我们返回开始的问题,那么怎么求取不是固定左上角坐标的区域值呢?

根据temp以及上述求取temp的过程,我们很容易想到:
num = temp[row2][col2] - temp[row1-1][col2] - temp[row2][col1-1] + temp[row1-1][col1-1];
结合下图去理解:
在这里插入图片描述
其中蓝色框为目标求取值,黑色框为temp[row2][col2],绿色框为temp[row2][col1-1],黄色框为temp[row1-1][col2],红色框为temp[row1-1][col1-1]。

完成上述工作后,我们可以很轻易的写出代码:

class NumMatrix {
    vector<vector<int>> temp;
public:
    NumMatrix(vector<vector<int>>& matrix) {
        if(!matrix.size()||!matrix[0].size())return;
        int m = matrix.size();
        int n = matrix[0].size();
        temp = vector<vector<int>>(m,vector<int>(n,0));
        
        for(int i=0;i<m;i++)
        {
            if(i>0)temp[i][0] = temp[i-1][0]+matrix[i][0];
            else temp[0][0] = matrix[0][0];
        }
        
        for(int i=0;i<n;i++)
        {
            if(i>0)temp[0][i] = temp[0][i-1]+matrix[0][i];
            else temp[0][0] = matrix[0][0];
        }
        
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                temp[i][j] = temp[i-1][j]+temp[i][j-1]+matrix[i][j]-temp[i-1][j-1];
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        int left=0,up=0,leftup=0;
        if(row1>0)up = temp[row1-1][col2];
        if(col1>0)left = temp[row2][col1-1];
        if(row1>0&&col1>0)leftup = temp[row1-1][col1-1];
        return temp[row2][col2]-left-up+leftup;
    }
};

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix* obj = new NumMatrix(matrix);
 * int param_1 = obj->sumRegion(row1,col1,row2,col2);
 */

代码评估如下:
在这里插入图片描述
时间复杂度为O(1),因为只用了数组查找,空间复杂度为O(mn),因为开辟了一个和原数组大小相同的数组来储存值。

这其实是在动态规划里面最经典的一类问题:前缀和,在很多动态规划类的题目中都有用到,所以掌握前缀和思想,是很重要的!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值