#力扣304 二维区域和检索
力扣304 二维区域和检索(前缀和数组)
一个计科小白的日更题解
(都是抄大佬们的思想,
加上一些自己的理解,
使得刚入门的小白也可以看),
帮助别人
也算是督促自己,
一起加油!
进阶自昨天的 303 .区域和检索 - 数组不可变
链接: link.
给定一个整数数组
n
u
m
s
nums
nums,求出数组从索引
i
i
i 到
j
j
j
(
i
≤
j
)
(i ≤ j)
(i≤j)范围内元素的总和,包含
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)
(i≤j)范围内元素的总和
,包含
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);//return−1(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);//return−3((−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[i−1]+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[m−1]、
这个式子看上去好像很对
但注意万一这个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
m∗n
那你要构建的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][col1−1]−preSum[row1−1][col2]+preSum[row1−1][col1−1]
作者: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];
}
};