力扣304 二维区域和检索

力扣304 二维区域和检索(前缀和数组)

一个计科小白的日更题解
(都是抄大佬们的思想,
加上一些自己的理解,
使得刚入门的小白也可以看),
帮助别人
也算是督促自己,
一起加油!

进阶自昨天的 303 .区域和检索 - 数组不可变

链接: link.
给定一个整数数组 n u m s nums nums,求出数组从索引 i i i j j j ( i ≤ j ) (i ≤ j) (ij)范围内元素的总和,包含 i i i j j j 两点。

实现 N u m A r r a y NumArray NumArray 类:

N u m A r r a y ( i n t [ ] n u m s ) NumArray(int[] nums) NumArray(int[]nums)使用数组 n u m s nums nums 初始化对象
i n t s u m R a n g e ( i n t i , i n t j ) int sumRange(int i, int j) intsumRange(inti,intj) 返回数组 n u m s nums nums 从索引 i i i j j j ( i ≤ j ) (i ≤ j) (ij)范围内元素的总和
,包含 i i i j j j 两点(也就是 s u m ( n u m s [ i ] , n u m s [ i + 1 ] , . . . , n u m s [ j ] sum(nums[i], nums[i + 1], ... , nums[j] sum(nums[i],nums[i+1],...,nums[j]))

示例:

输入:
[ " N u m A r r a y " , " s u m R a n g e " , " s u m R a n g e " , " s u m R a n g e " ] ["NumArray", "sumRange", "sumRange", "sumRange"] ["NumArray","sumRange","sumRange","sumRange"]
[ [ [ − 2 , 0 , 3 , − 5 , 2 , − 1 ] ] , [ 0 , 2 ] , [ 2 , 5 ] , [ 0 , 5 ] ] [[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]] [[[2,0,3,5,2,1]],[0,2],[2,5],[0,5]]
输出:
[ n u l l , 1 , − 1 , − 3 ] [null, 1, -1, -3] [null,1,1,3]

解释:
N u m A r r a y n u m A r r a y = n e w N u m A r r a y ( [ − 2 , 0 , 3 , − 5 , 2 , − 1 ] ) ; NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]); NumArraynumArray=newNumArray([2,0,3,5,2,1]);
n u m A r r a y . s u m R a n g e ( 0 , 2 ) ; / / r e t u r n 1 ( ( − 2 ) + 0 + 3 ) numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3) numArray.sumRange(0,2);//return1((2)+0+3)
n u m A r r a y . s u m R a n g e ( 2 , 5 ) ; / / r e t u r n − 1 ( 3 + ( − 5 ) + 2 + ( − 1 ) ) numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1)) numArray.sumRange(2,5);//return1(3+(5)+2+(1))
n u m A r r a y . s u m R a n g e ( 0 , 5 ) ; / / r e t u r n − 3 ( ( − 2 ) + 0 + 3 + ( − 5 ) + 2 + ( − 1 ) ) numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1)) numArray.sumRange(0,5);//return3((2)+0+3+(5)+2+(1))

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/range-sum-query-immutable
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这是一个非常标准的用前缀和数组解决的问题
及其类似一些班级上机的第一题
比如一个数组a
[3,5,7,8,1]
我们可以得到相对应的前缀和数组sum
[3,8,15,23,24]

也就是
s u m [ 0 ] = a [ 0 ] = 3 sum[0]=a[0]=3 sum[0]=a[0]=3
s u m [ 1 ] = a [ 0 ] + a [ 1 ] = 3 + 5 = 8 sum[1]=a[0]+a[1]=3+5=8 sum[1]=a[0]+a[1]=3+5=8
s u m [ 2 ] = a [ 0 ] + a [ 1 ] + a [ 2 ] = 3 + 5 + 7 = 15 sum[2]=a[0]+a[1]+a[2]=3+5+7=15 sum[2]=a[0]+a[1]+a[2]=3+5+7=15
s u m [ 3 ] = a [ 0 ] + a [ 1 ] + a [ 2 ] + a [ 3 ] = 3 + 5 + 7 + 8 = 23 sum[3]=a[0]+a[1]+a[2]+a[3]=3+5+7+8=23 sum[3]=a[0]+a[1]+a[2]+a[3]=3+5+7+8=23
s u m [ 4 ] = a [ 0 ] + a [ 1 ] + a [ 2 ] + a [ 3 ] + a [ 4 ] = 3 + 5 + 7 + 8 + 1 = 24 sum[4]=a[0]+a[1]+a[2]+a[3]+a[4]=3+5+7+8+1=24 sum[4]=a[0]+a[1]+a[2]+a[3]+a[4]=3+5+7+8+1=24

而题意就是比如给你一个数组a
给你多组数组下标
比如 1 3就是让你返回 a [ 1 ] + a [ 2 ] + a [ 3 ] a[1]+a[2]+a[3] a[1]+a[2]+a[3]的值
聪明的小伙伴一定看出了规律
给你1 3
那我就用 s u m [ 3 ] − s u m [ 0 ] = a [ 1 ] + a [ 2 ] + a [ 3 ] sum[3]-sum[0]=a[1]+a[2]+a[3] sum[3]sum[0]=a[1]+a[2]+a[3]就可以了
这也就是前缀和数组最基本的应用。

特点:
s u m [ i ] = s u m [ i − 1 ] + a [ i ] sum[i]=sum[i-1]+a[i] sum[i]=sum[i1]+a[i]
∑ m n a i = s u m [ n ] − s u m [ m − 1 ] \sum_m^na_i=sum[n]-sum[m-1] mnai=sum[n]sum[m1]

这个式子看上去好像很对
但注意万一这个m恰好是0呢
我给你一个从0到3
你心里明白是让我用 s u m [ 3 ] − 0 sum[3]-0 sum[3]0
但是 s u m [ − 1 ] sum[-1] sum[1]显然很容易出现问题

所以后来出现了一种新的形式
对于数组a
[3,5,7,8,1]
长度为5
我设一个长度为6的前缀和数组
我让 s u m [ 0 ] = 0 sum[0]=0 sum[0]=0
剩下的依次递推

s u m [ 1 ] = a [ 0 ] = 3 sum[1]=a[0]=3 sum[1]=a[0]=3
s u m [ 2 ] = a [ 0 ] + a [ 1 ] = 3 + 5 = 8 sum[2]=a[0]+a[1]=3+5=8 sum[2]=a[0]+a[1]=3+5=8
s u m [ 3 ] = a [ 0 ] + a [ 1 ] + a [ 2 ] = 3 + 5 + 7 = 15 sum[3]=a[0]+a[1]+a[2]=3+5+7=15 sum[3]=a[0]+a[1]+a[2]=3+5+7=15
s u m [ 4 ] = a [ 0 ] + a [ 1 ] + a [ 2 ] + a [ 3 ] = 3 + 5 + 7 + 8 = 23 sum[4]=a[0]+a[1]+a[2]+a[3]=3+5+7+8=23 sum[4]=a[0]+a[1]+a[2]+a[3]=3+5+7+8=23
s u m [ 5 ] = a [ 0 ] + a [ 1 ] + a [ 2 ] + a [ 3 ] + a [ 4 ] = 3 + 5 + 7 + 8 + 1 = 24 sum[5]=a[0]+a[1]+a[2]+a[3]+a[4]=3+5+7+8+1=24 sum[5]=a[0]+a[1]+a[2]+a[3]+a[4]=3+5+7+8+1=24

这样会得到新的规律
∑ m n a i = s u m [ n + 1 ] − s u m [ m ] \sum_m^na_i=sum[n+1]-sum[m] mnai=sum[n+1]sum[m]

这样重新考虑
我给一个从0到3
这下子是不是就变成了 s u m [ 4 ] − s u m [ 0 ] = ( a [ 0 ] + a [ 1 ] + a [ 2 ] + a [ 3 ] ) − 0 sum[4]-sum[0]=(a[0]+a[1]+a[2]+a[3])-0 sum[4]sum[0]=(a[0]+a[1]+a[2]+a[3])0
是不是就没有任何问题了

同理可得,如果题目中给从1到3
我就可以用 s u m [ 4 ] − s u m [ 1 ] sum[4]-sum[1] sum[4]sum[1]

那这个时候有的人会想
你让我用这个还挺麻烦的
你让我求我就求得了呗
从1到3一项项加 a [ 1 ] + a [ 2 ] + a [ 3 ] a[1]+a[2]+a[3] a[1]+a[2]+a[3]才加了两次
而你这么做得从0加到3 加了三次 显然更加麻烦
这个确实,但是前缀和数组显然不是让你这么用的

前缀和数组最适用于多组求和
比如现在是一个大小为10000的数组
我让你求 1000次
每次都是从很小的项数加到很大的项数(比如从第3项加到第9999项)
这样的话1000*9996显然是一个天文数字
但你如果用前缀和数组 你只需要计算10000次去构建前缀和数组
之后每次计算一次就可以求得任何一项到任何一项之间所有项的和

用计算机的术语来说
如果你不构建,你每次计算的时间复杂度是 o ( n ) o(n) o(n)
但如果你构建了前缀数组,你每次计算的时间复杂度是 o ( 1 ) o(1) o(1)
如果乘以次数m次
如果你不构建,你总的计算的时间复杂度是 o ( m n ) o(mn) o(mn)
但如果你构建了前缀数组,你总的计算的时间复杂度是 o ( m ) o(m) o(m)
可见,前缀数组在大数据的处理上有其独到之处。
代码

class NumArray {
public:
    vector<int> sums;
    NumArray(vector<int>& nums) {
        int n = nums.size();
        sums.resize(n + 1);//修改长度为原长度+1
        for (int i = 0; i < n; i++) {
            sums[i + 1] = sums[i] + nums[i];//计算前缀和数组
        }
    }//构造函数
    int sumRange(int i, int j) {
        return sums[j + 1] - sums[i];//计算区域元素和
    }
};

这是标准答案,也没啥可说的,真的很标准!

二维区域

再回到今天的题目
昨天是一维区域上的前缀数组,
今天是二维

链接: link.
304. 二维区域和检索 - 矩阵不可变
给定一个二维矩阵,计算其子矩形范围内元素的总和,
该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。

上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),
该子矩形内元素的总和为 8。

示例:

给定
matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]

sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/range-sum-query-2d-immutable
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

和上次的思想基本一致,
如果给的二维数组长度为 m ∗ n m*n mn
那你要构建的sum数组大小必须为 ( m + 1 ) ∗ ( n + 1 ) (m+1)*(n+1) (m+1)(n+1)
比如给你一个数组
1 2
3 4
那你要构建的前缀和数组应该是
0 0 0
0 1 3
0 4 10
这么看有点难以理解(跟1维比)

其实就是那个3
就是子矩阵 1 2(行矩阵)的元素和

那个4
就是子矩阵(列矩阵)
1
3
的元素和

(如果不太懂可以看底下大佬的图解,
再结合我这个例子
相信会有新的体会。)

以此为原理构造新的sum数组
之后再同理求二维前缀和

引用力扣一个非常非常优秀的题解作者负雪明烛的配图和题解

步骤一:求 p r e S u m preSum preSum

我们先从如何求出二维空间的 p r e S u m [ i ] [ j ] preSum[i][j] preSum[i][j]

我们定义 p r e S u m [ i ] [ j ] preSum[i][j] preSum[i][j]
p r e S u m [ i ] [ j ] preSum[i][j] preSum[i][j] 表示 从 [ 0 , 0 ] [0,0] [0,0] 位置到 [ i , j ] [i,j] [i,j] 位置的子矩形所有元素之和。
可以用下图帮助理解:

S ( O , D ) = S ( O , C ) + S ( O , B ) − S ( O , A ) + D S(O,D)=S(O,C)+S(O,B)−S(O,A)+D S(O,D)=S(O,C)+S(O,B)S(O,A)+D
在这里插入图片描述

步骤二:根据 p r e S u m preSum preSum 求子矩形面积

前面已经求出了数组中从 [ 0 , 0 ] [0,0] [0,0]位置到 [ i , j ] [i,j] [i,j] 位置的 p r e S u m preSum preSum
下面要利用 p r e S u m [ i ] [ j ] preSum[i][j] preSum[i][j]
来快速求出任意子矩形的面积。

同样利用一张图来说明:

在这里插入图片描述

加上子矩形 S ( O , G ) S(O, G) S(O,G) 面积的原因是 S ( O , E ) S(O, E) S(O,E) S ( O , F ) S(O, F) S(O,F) 中都有 S ( O , G ) S(O, G) S(O,G),即减了两次 S ( O , G ) S(O, G) S(O,G)所以需要加上一次 S ( O , G ) S(O, G) S(O,G)

如果要求 [ r o w 1 , c o l 1 ] [row1, col1] [row1,col1] [ r o w 2 , c o l 2 ] [row2, col2] [row2,col2] 的子矩形的面积的话,用 p r e S u m preSum preSum对应了以下的递推公式:

p r e S u m [ r o w 2 ] [ c o l 2 ] − p r e S u m [ r o w 2 ] [ c o l 1 − 1 ] − p r e S u m [ r o w 1 − 1 ] [ c o l 2 ] + p r e S u m [ r o w 1 − 1 ] [ c o l 1 − 1 ] preSum[row2][col2]−preSum[row2][col1−1]−preSum[row1−1][col2]+preSum[row1−1][col1−1] preSum[row2][col2]preSum[row2][col11]preSum[row11][col2]+preSum[row11][col11]

作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/ru-he-qiu-er-wei-de-qian-zhui-he-yi-ji-y-6c21/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码给的是python的,我给一下标答中c++的

class NumMatrix {
public:
    vector<vector<int>> sums;
    NumMatrix(vector<vector<int>>& matrix) {
        int m=matrix.size();//行数
        if(m>0)
        {
        int n=matrix[0].size();//列数
        sums.resize(m+1,vector<int>(n+1));//重新定义数组大小为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];
            }
        }
    }//构造函数 构造sum数组
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2+1][col2+1]-sums[row2+1][col1]-sums[row1][col2+1]+sums[row1][col1];
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值