Leetcode1074:元素和为目标值的子矩阵数量(困难!前缀和+哈希表)

题目:
给出矩阵 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;
    }
};
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值