题目:
给出矩阵 matrix 和目标值 target,返回元素总和等于目标值的非空子矩阵的数量。
思路:
这道题目和363.矩形区域不超过 K 的最大数值和 、560.和为K的子数组、2.两数之和 有类似的地方。可以先去做这些题目再回来解答本题。
假设有前缀和知识的储备,看到这道题目可能会想到用二维前缀和来搜索数组。如何实现?
首先一个数组实现二维前缀和的代码如下:
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
在实现前缀和的基础上,我们需要获得包含各种子矩阵的和,如何做到?这个可以参考下面的代码。我们每次以i j作为当前最大矩阵右下角标号,该矩阵包含的子矩阵肯定是在他的左上方。考虑到我们i j循环过程中每次都是增加一个元素,因此,对每次的包络最大矩阵新增加的子矩阵只是包含新的 i j 坐标处的所有矩阵,其他的矩阵在之前遍历时已经检索过了。 因此,在每个包络矩阵中我们增加了 p q,p代表这个x方向的截至线,q代表这个y方向的截止线。注意到相当于p从0开始一直到i-1,q从j-1开始一直到0,或者为了更好理解。反过来, p从i-1k开始,这时候每次q从j-1开始到0,然后这两条截止线帮助筛选出包括右下角的所有新子集~~ 下面的代码p q=1.增加了1呗,那下面就减少1!
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int p = 1; p <= i; p++) {
for (int q = 1; q <= j; q++) {
if (sum[i][j] - sum[p - 1][j] - sum[i][q - 1] + sum[p - 1][q - 1] == t) ans++;
}
}
}
}
综上,暴力前缀和搜索的代码Java如下:
class Solution {
public int numSubmatrixSumTarget(int[][] mat, int t) {
int n = mat.length, m = mat[0].length;
int[][] sum = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int p = 1; p <= i; p++) {
for (int q = 1; q <= j; q++) {
if (sum[i][j] - sum[p - 1][j] - sum[i][q - 1] + sum[p - 1][q - 1] == t) ans++;
}
}
}
}
return ans;
}
}
然而这个方法会超时,时间复杂度最大是o(m2n2),虽然没到。但是还不行。
进一步地了,Leetcode官方题解给出了前缀和+哈希表的方法优化时间复杂度。基本思路是我们通过枚举上边界和下边界,总边界长度是m,他们可以重合。一共的循环次数就是m+m-1…+1,总的复杂度就是m的平方了。假设只有一列,这就是所有的子矩阵了。那如何对列增加时候枚举出来所有组合呢。
我们知道在上述循环中某个情况下,我知道了某一行是上边界,一共有很多个下边界,我用一个sum的一维数组,维度和列数一致,用来记录当前上边界下某个下边界的列累加和,将每列累加记录在sum对应的空间里。每次记录完一个下边界,就获得一组数据,接下来就对sum进行处理搜索。
我们知道假设列=3,一共的组合有6个子集,用暴力法需要找6此。有没有更快的办法呢?我们是否可以用3次搜索来完成呢。其实是可以的,这里题目需要的target值我们是知道的。因此我们在遍历sum的每个元素时计算他的前缀和,然后存入哈希表,注意到我们每次遍历到这个累加值时,他中间的子集来自累加值pre减去某一次的累加值pre。要是中间某个值等于target, pre过去+target=pre现在,所以一旦pre-target在哈希表中出现就说明存在一个子集之和等于target,而且因为有负数,这个子集数量还不一定1呢,所以加上pre-k的个数才对。而且这个累加值一定是包含当前矩阵快的子集,所以和之前的不同,要叠加。注意到mp[0]=1.就是考虑到我们每次只是将前缀和记录在哈希表里面,pre-k是对之前元素的检索,但是如果我们想要对当前整体元素的检索不能做到,就是当pre-k等于0时候,我们要给他加1的!
class Solution {
private:
int subarraySum(vector<int> &nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1;
int count = 0, pre = 0;
for (auto &x:nums) {
pre += x;
if (mp.find(pre - k) != mp.end()) {
count += mp[pre - k];
}
mp[pre]++;
}
return count;
}
public:
int numSubmatrixSumTarget(vector<vector<int>> &matrix, int target) {
int ans = 0;
int m = matrix.size(), n = matrix[0].size();
for (int i = 0; i < m; ++i) { // 枚举上边界
vector<int> sum(n);
for (int j = i; j < m; ++j) { // 枚举下边界
for (int c = 0; c < n; ++c) {
sum[c] += matrix[j][c]; // 更新每列的元素和
}
ans += subarraySum(sum, target);
}
}
return ans;
}
};