题目描述:
给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:
NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。
示例:
输入:
["NumMatrix","sumRegion","sumRegion","sumRegion"]
[[[[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]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]
算法思想:
在数组matrix中,构建二维前缀和数组sum,从而将对matrix上(row2,col2)到(row1,col1)之间元素的和转化为O(1)时间的求和。
二维前缀和构造公式:
S [ i ] [ j ] = ∑ x = 1 i ∑ y = 1 j A [ x ] [ y ] = S [ i − 1 ] [ j ] + S [ i ] [ j − 1 ] − S [ i − 1 ] [ j − 1 ] + A [ i ] [ j ] S[i][j] = \sum_{x = 1}^{i} \sum_{y=1}^{j}A[x][y] = S[i-1][j] + S[i][j-1] -S[i-1][j-1] + A[i][j] S[i][j]=∑x=1i∑y=1jA[x][y]=S[i−1][j]+S[i][j−1]−S[i−1][j−1]+A[i][j]
二维前缀和计算公式:
s u m ( p , q , i , j ) = ∑ x = p i ∑ y = q j A [ x ] [ y ] = S [ i ] [ j ] − S [ i ] [ q − 1 ] − S [ p − 1 ] [ j ] + S [ p − 1 ] [ q − 1 ] sum(p,q,i,j) = \sum_{x = p}^{i} \sum_{y=q}^{j} A[x][y] = S[i][j] - S[i][q-1] - S[p-1][j] + S[p-1][q-1] sum(p,q,i,j)=∑x=pi∑y=qjA[x][y]=S[i][j]−S[i][q−1]−S[p−1][j]+S[p−1][q−1]
c++代码实现:
class NumMatrix {
public:
NumMatrix(vector<vector<int>>& matrix) {
int n = matrix.size();
int m = matrix[0].size();
// 将sum二维矩阵resize成 n+1 行 m+1 列且初始化为 0 的写法:
sum.resize(n + 1,vector<int>(m+1,0));
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
// 构建 二维前缀和矩阵, 由于matrix数组是从0开始的,所有构建前缀和数组时要将下标都-1挪一位
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + matrix[i-1][j-1];
}
int sumRegion(int row1, int col1, int row2, int col2) {
row1 ++, col1++, row2++, col2++;
// 返回二维前缀和数组 (row2,col2)到(row1,col1)之间的元素和
return sum[row2][col2] - sum[row2][col1 -1] - sum[row1-1][col2] + sum[row1-1][col1-1];
}
private:
vector<vector<int>> sum;
};
题目描述:
给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中 「优美子数组」 的数目。
示例:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
算法思路:
1、将数组nums中每个元素对2取模,则数组中奇数元素变为1,偶数元素变为0,原问题连续子数组中有k个奇数的问题就转化为求和为k的连续子数组的个数。对一段连续数组求和可以使用 前缀和 来简化问题。
2、对数组nums对2取余后求其前缀和数组后,问题转化为求
s
[
r
]
−
s
[
l
−
1
]
=
=
k
s[r] - s[l-1] == k
s[r]−s[l−1]==k的连续子段个数,可以分别遍历i,j求满足上式的个数。
代码实现:
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int ans = 0;
vector<int> a(nums.size()+1);
vector<int> s(nums.size()+1);
for(int i = nums.size(); i >= 1; i--) a[i] = nums[i-1] % 2;
s[0] = 0;
for(int i = 1; i <= nums.size(); i++) s[i] = s[i-1] + a[i];
for(int i = 1; i <= nums.size(); i++){
for(int j = 1; j <= i; j++)
if(s[i] - s[j-1] == k)
ans ++;
}
return ans;
}
};
这种方法时间复杂度是O(n^2)会超时,所以考虑优化。
3、固定 r,观察l的变化:
1、r的取值范围是1~n 在前缀和数组上从1开始到n,l的取值范围是 1 ~ r;
2、由于求的是s[r] - s[l-1] == k的值,所以考虑 换元令i=r, j = l-1,可知,i的范围是 1~n, j 的范围是 0 ~ i-1;
3、问题转变为 求s[i] - s[j] == k的个数,即满足s[j] == s[i] - k 的 j的个数,其中 i 的取值范围是1~n; j的取值范围是 0~i-1;
4、问题变为求s[] 数组中等于 s[i]- k 的值的个数,可以使用hash表统计s[]数组各个值的个数,即可求出s数组中等于 s[i] -k的个数。
C++代码实现:
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int n = nums.size();
vector<int>sum(n+1,0);
// 构造nums数组对2取模后的前缀和数组sum,细节 nums[i-1]
for(int i = 1; i <= n; i++) sum[i] = sum[i-1] + nums[i-1] % 2;
// cout << " 打印sum数组:" << endl;
// for(int i = 1; i <= n; i++) cout << sum[i] << " ";
int ans = 0;
// 使用hash数组统计sum数组各个元素值的个数。
unordered_map<int,int> hash;
// 由于数组中s[i] - k = 0的值,所以需要记录sum数组中元素0的个数。
for(int i = 0; i <= n; i++){
// j 范围是 0 ~ i-1, 所以得满足sum[i] - k >= 0;
// 求sum数组中等于 sum[i] - k 的值的个数。
if(sum[i] - k >= 0) ans += hash[sum[i] - k];
hash[sum[i]] ++;
}
return ans;
}
};
时间复杂度降为O(n),空间复杂度:O(n)。