LeetCode 1074 元素为目标值的子矩阵数量

本文介绍了LeetCode 1074题目的解决方法,主要探讨了两种策略:一是利用前缀和计算子矩阵元素总和,时间复杂度为O(row² * col²);二是结合前缀和与哈希表优化,将时间复杂度降低到O(row² * col),并通过调整哈希函数参数进一步优化至O(row²)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LeetCode 1074 元素为目标值的子矩阵数量

题目

给出矩阵 matrix 和目标值 target,返回元素总和等于目标值的非空子矩阵的数量。

子矩阵 x1, y1, x2, y2 是满足 x1 <= x <= x2y1 <= y <= y2 的所有单元 matrix[x][y] 的集合。

如果 (x1, y1, x2, y2)(x1', y1', x2', y2') 两个子矩阵中部分坐标不同 (如:x1 != x1’),那么这两个子矩阵也不同。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-submatrices-that-sum-to-target
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路一 前缀和

定义rowcol 分别对应矩阵的行数和列数。

一个直观的思路是使用前缀和。定义 p r e f i x [ x ] [ y ] prefix[x][y] prefix[x][y] 表示从 [0, 0][x, y] 的自矩阵元素综合
p r e f i x [ x ] [ y ] = ∑ i = 0 x ∑ j = 0 y m a t r i x [ x ] [ y ] prefix[x][y]=\sum_{i=0}^x\sum_{j=0}^{y}matrix[x][y] prefix[x][y]=i=0xj=0ymatrix[x][y]
生成此矩阵需要 O ( r o w ⋅ c o l ) O(row\cdot col) O(rowcol) 时间复杂度,基于如下递推公式:
p r e f i x [ x ] [ y ] = m a t r i x [ x ] [ y ] + p r e f i x [ x − 1 ] [ y ] + p r e f i x [ x ] [ y − 1 ] − p r e f i x [ x − 1 ] [ y − 1 ] prefix[x][y] = matrix[x][y] + prefix[x-1][y] +\\ prefix[x][y -1] -prefix[x- 1][y - 1] prefix[x][y]=matrix[x][y]+prefix[x1][y]+prefix[x][y1]prefix[x1][y1]
生成前缀和矩阵后,可以在 O ( 1 ) O(1) O(1) 时间内计算任意子矩阵元素和
r e t [ i , j , x , y ] = p r e f i x [ x ] [ y ] − p r e f i x [ i − 1 ] [ y ] − p r e f i x [ x ] [ j − 1 ] + p r e f i x [ i − 1 ] [ j − 1 ] ret[i, j, x, y] = prefix[x][y] -prefix[i - 1][y] \\-prefix[x][j - 1] + prefix[i - 1][j - 1] ret[i,j,x,y]=prefix[x][y]prefix[i1][y]prefix[x][j1]+prefix[i1][j1]
[i, j, x, y] 表示起始坐标是[i, j], 中止坐标[x, y] 注意这两个公式都需要考虑一下边界条件。

那么思路是枚举每一对起始和终止坐标,时间复杂度为 O ( r o w 2 ⋅ c o l 2 ) O(row^2\cdot col^2) O(row2col2)

实测大概是 600ms,代码如下,注意边界处理

class Solution {
public:
    int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
        int row = matrix.size(), col = matrix[0].size();
        int prefix[row][col];
        memset(prefix, 0, sizeof(prefix));
        int ret = 0;
        for(int x = 0; x < row; ++x){
            for(int y = 0; y < col; ++y){
                if (x == 0 && y == 0){
                    prefix[x][y] = matrix[x][y];
                }
                else if(x == 0){
                    prefix[x][y] = matrix[x][y] + prefix[x][y - 1];
                }
                else if(y == 0){
                    prefix[x][y] = matrix[x][y] + prefix[x - 1][y];
                }
                else prefix[x][y] = matrix[x][y] + prefix[x - 1][y] + prefix[x][y - 1] - prefix[x - 1][y - 1];
                for (int i = 0; i <= x; ++ i){
                    for(int j = 0; j <= y; ++j){
                        int c =  i && j ? prefix[i - 1][j - 1]: 0;
                        int a = i ? prefix[i - 1][y]: 0;
                        int b = j ? prefix[x][j - 1]: 0;
                        int tmp = prefix[x][y] - a - b + c;
                        if (tmp == target) ++ ret;
                    }
                }
            }
        }
        return ret;
    }
};

思路二 前缀和+哈希表优化

定义一维前缀和矩阵 prefix,对应每一列元素的前缀和
p r e f i x [ x ] [ y ] = ∑ i x m a t r i x [ i ] [ y ] prefix[x][y] = \sum_{i}^xmatrix[i][y] prefix[x][y]=ixmatrix[i][y]
那么通过哈希表可以在 O ( c o l ) O(col) O(col) 时间内计算某两行之间符合要求的子矩阵数量。过程如下:

  1. 选取两行r1, r2
  2. 创建哈希表counter[k] = v分别为前缀和和出现次数 counter[0] = 1
  3. 记录前缀和cSum = 0, 初始化符合要求的子矩阵计数ret = 0
  4. 枚举 c = 0,...col
    1. cSum += prefix[r2][c] - prefix[r1 - c][c]
    2. ret += counter[cSum - target]
    3. counter[cSum] += 1

在这种情况下,总体上需要枚举所有的起始和终止行,时间复杂度为 O ( r o w 2 ⋅ c o l ) O(row^2\cdot col) O(row2col)

代码如下,用时约1800ms,原因在于哈希表在内循环结束后需要清空。

class Solution {
public:
    int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
        int row = matrix.size(), col = matrix[0].size();
        int accRow[row][col];
        memset(accRow, 0, sizeof(accRow));
        for(int i = 0; i < row; ++i)
            for(int j = 0; j < col; ++j){
                if (i == 0) accRow[i][j] = matrix[i][j];
                else accRow[i][j] = matrix[i][j] + accRow[i - 1][j];
            }
        
        unordered_map<int, int> preSumCnt;
        int ret = 0;
        preSumCnt[0] = 1;
        for(int sr = 0; sr < row; ++sr){
            for(int er = sr; er < row; ++er){
                int cSum = 0;
                for(int c = 0; c < col; ++c){
                    cSum += accRow[er][c] - (sr? accRow[sr - 1][c]: 0);
                    ret += preSumCnt[cSum - target];
                    ++preSumCnt[cSum];
                }
                preSumCnt.clear(); //此处时间复杂度较高
                preSumCnt[0] = 1;
            }
        }
        return ret;
    }
};

可以通过手写哈希表来优化,参考 oi.wiki的代码,实测约 90ms。这里有点调参的意思,选取 97 作为哈希函数参数时,总共映射的 key 不超过 97 * 4 个。每个循环中清空哈希表的耗时会小很多。

const int SZ = 97;

struct hash_map {  // 哈希表模板

  struct data {
    long long u;
    int v, nex;
  };                // 前向星结构
  
  data e[SZ << 2];  // SZ 是 const int 表示大小 调参
  int h[SZ], cnt;
  int hash(long long u) {
      int ret = u % SZ; 
      return ret > -1? ret: ret + SZ; 
      }

  int& operator[](long long u) {
    int hu = hash(u);  // 获取头指针
    for (int i = h[hu]; i; i = e[i].nex)
      if (e[i].u == u) return e[i].v;
    return e[++cnt] = (data){u, 0, h[hu]}, h[hu] = cnt, e[cnt].v;
  }

  void clear(){
      cnt = 0;
      memset(h, 0, sizeof(h));
  }
  hash_map() {
    cnt = 0;
    memset(h, 0, sizeof(h));
  }
};


class Solution {
public:
    int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
        int row = matrix.size(), col = matrix[0].size();
        int prefix[row][col];
        memset(prefix, 0, sizeof(prefix));
        for (int i = 0; i < row; ++i)
            // O(row * col)
            for(int j = 0; j < col; ++j){
                prefix[i][j] = matrix[i][j] + (i? prefix[i - 1][j]: 0);
            }
        int ret = 0;
        hash_map mp;
        mp[0] = 1;
        for (int sr = 0; sr < row; ++sr){
            for(int er = sr; er < row; ++er){
                int cSum = 0;
                for(int c = 0; c < col; ++c){
                    cSum += prefix[er][c] - (sr? prefix[sr - 1][c]: 0);
                    ret += mp[cSum - target];
                    ++mp[cSum]; 
                }
                mp.clear();
                mp[0] = 1;
            }
        }
        return ret;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值