Leetcode(303)——区域和检索 - 数组不可变
题目
给定一个整数数组 nums,处理以下类型的多个查询:
计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
实现 NumArray 类:
- NumArray(int[] nums) 使用数组 nums 初始化对象
- int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )
示例 1:
输入:
[“NumArray”, “sumRange”, “sumRange”, “sumRange”]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]
解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))
提示:
- 1 <= nums.length <= 1 0 4 10^4 104
- − 1 0 5 -10^5 −105 <= nums[i] <= 1 0 5 10^5 105
- 0 <= i <= j < nums.length
- 最多调用 1 0 4 10^4 104 次 sumRange 方法
题解
方法一:暴力枚举
思路
1.
代码实现
复杂度分析
时间复杂度:
O
(
)
O()
O() ,其中 $$ 是。
空间复杂度:
O
(
)
O()
O()
方法二:保存从任意下标 i 到另一下标 r 的所有结果
思路
在这里
代码实现
class NumArray {
private:
// vector 也可以,不一定要 unordered_map
unordered_map<int, unordered_map<int, int>> lr_value; // 保存l到r的结果
public:
NumArray(vector<int>& nums){
// 初始化 NumArray 时就计算好所有的结果,对应存给lr_value[i][j]
int curr;
int ans = 0;
for(int a = 0; a < nums.size(); a++){
for(int b = a; b < nums.size(); b++){
curr = a;
while(curr <= b){
ans += nums[curr++];
}
lr_value[a][b] = ans;
ans = 0; // 重置ans
}
}
}
int sumRange(int left, int right) {
return lr_value[left][right];
}
};
复杂度分析
时间复杂度:
O
(
N
3
)
O(N^3)
O(N3) ,其中
N
N
N 是数组 nums 中的元素个数。因为最外面两层循环的时间复杂度为
O
(
N
2
)
O(N^2)
O(N2) ,而内层循环的时间复杂度为
O
(
N
)
O(N)
O(N),所以总时间复杂度为
O
(
N
3
)
O(N^3)
O(N3)。
空间复杂度:
O
(
N
2
)
O(N^2)
O(N2) ,其中
N
N
N 是数组 nums 中的元素个数。
方法三:采用 DP 思想优化保存过程
思路
我们在这里采用 DP 思想对方法二进行一部分优化:在内循环中不再重复求之前的结果,而是直接将其拿来与当前值相加即可获得所要值。
代码实现
class NumArray {
private:
// vector 也可以,不一定要 unordered_map
unordered_map<int, unordered_map<int, int>> lr_value; // 保存l到r的结果
public:
NumArray(vector<int>& nums){
// 初始化 NumArray 时就计算好所有的结果,对应存给lr_value[i][j]
for(int a = 0; a < nums.size(); a++){
for(int b = a; b < nums.size(); b++){
// 采用DP思想,将之前的结果拿来使用
if(a == b)
lr_value[a][b] = nums[a];
else lr_value[a][b] = lr_value[a][b-1] + nums[b];
}
}
}
int sumRange(int left, int right) {
return lr_value[left][right];
}
};
复杂度分析
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2) ,其中
N
N
N 是数组 nums 中的元素个数。因为最外面两层循环的时间复杂度为
O
(
N
2
)
O(N^2)
O(N2) ,而内层循环的时间复杂度为
O
(
1
)
O(1)
O(1),所以总时间复杂度为
O
(
N
2
)
O(N^2)
O(N2)。
空间复杂度:
O
(
N
2
)
O(N^2)
O(N2) ,其中
N
N
N 是数组 nums 中的元素个数。
方法四:前缀和
思路
在这里我们学习一个新算法——前缀和():前缀和是指某序列的前 n 项和,可以把它理解为数学上的数列的前 n 项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。
nums 数组的每一项都对应有它的前缀和: nums 的第 0 项到 当前项 的和。用数组 preSum 表示,则
p
r
e
S
u
m
[
i
]
preSum[i]
preSum[i] 表示:第 0 项到 第 i 项 的和:
p
r
e
S
u
m
[
i
]
=
n
u
m
s
[
0
]
+
n
u
m
s
[
1
]
+
…
+
n
u
m
s
[
i
]
preSum[i] = nums[0] + nums[1] +…+nums[i]
preSum[i]=nums[0]+nums[1]+…+nums[i]
我们可以很容易知道,nums 的某项 = 两个相邻前缀和的差:
n
u
m
s
[
i
]
=
p
r
e
S
u
m
[
i
]
−
p
r
e
S
u
m
[
i
−
1
]
nums[i] = preSum[i] - preSum[i - 1]
nums[i]=preSum[i]−preSum[i−1]
对于 nums 的 i 到 j 的元素和,上式叠加,有:
n
u
m
s
[
i
]
+
…
+
n
u
m
s
[
j
]
=
p
r
e
S
u
m
[
j
]
−
p
r
e
S
u
m
[
i
−
1
]
nums[i] +…+nums[j]=preSum[j] - preSum[i - 1]
nums[i]+…+nums[j]=preSum[j]−preSum[i−1]
当 i 为 0 时,此时 i-1 为 -1,我们故意让preSum[-1]为 0,使得上式在i=0时也成立:
n
u
m
s
[
0
]
+
…
+
n
u
m
s
[
j
]
=
p
r
e
S
u
m
[
j
]
nums[0] +…+nums[j]=preSum[j]
nums[0]+…+nums[j]=preSum[j]
所以:
s
u
m
R
a
n
g
e
(
i
,
j
)
=
p
r
e
S
u
m
[
j
]
−
p
r
e
S
u
m
[
i
−
1
]
sumRange(i, j)=preSum[j] - preSum[i - 1]
sumRange(i,j)=preSum[j]−preSum[i−1]
我们在初始化阶段求出 preSum 数组的每一项,求 sumRange(i,j) 时直接返回 preSum[j] - preSum[i-1]
怎么求 preSum 数组?
利用前面提到的递推式:
p
r
e
S
u
m
[
i
]
=
p
r
e
S
u
m
[
i
−
1
]
+
n
u
m
s
[
i
]
preSum[i] = preSum[i - 1]+nums[i]
preSum[i]=preSum[i−1]+nums[i]
遍历求出每一个
p
r
e
S
u
m
[
i
]
preSum[i]
preSum[i],别忘了预置
p
r
e
S
u
m
[
−
1
]
=
0
preSum[-1] = 0
preSum[−1]=0,即
p
r
e
S
u
m
[
0
]
=
n
u
m
s
[
0
]
+
p
r
e
S
u
m
[
−
1
]
preSum[0] = nums[0] + preSum[-1]
preSum[0]=nums[0]+preSum[−1] (前提是 nums 有元素)。
预置
p
r
e
S
u
m
[
−
1
]
preSum[-1]
preSum[−1] 这种情况,只是为了边界情况也能套用通式。
代码实现
class NumArray {
private:
vector<int> Presum;
public:
NumArray(vector<int>& nums) {
int n = nums.size();
Presum.resize(n+1);
for(int i = 1; i < n+1; i++)
{
Presum[i] = Presum[i-1] + nums[i-1];
}
}
int sumRange(int left, int right) {
return Presum[right+1] - Presum[left];
}
};
复杂度分析
时间复杂度:初始化时的时间复杂度
O
(
N
)
O(N)
O(N) ,其中
N
N
N 是数组 nums 的元素个数。而获取结果,即调用函数 sumRange 的时间复杂度为
O
(
1
)
O(1)
O(1)
空间复杂度:
O
(
N
)
O(N)
O(N)