# 1074. 元素和为目标值的子矩阵数量 (Hard)

## 问题描述

[1074. 元素和为目标值的子矩阵数量][link] (Hard)

[link]: https://leetcode.cn/problems/number-of-submatrices-that-sum-to-target/

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

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

如果 `(x1, y1, x2, y2)` 和 `(x1', y1', x2', y2')` 两个子矩阵中部分坐标不同（如： `x1 != x1'`），那么
这两个子矩阵也不同。

**示例 1：**

![](https://pic-upyun.zwyyy456.tech/smms/2023-12-26-065525.jpg)

```
输入：matrix = [[0,1,0],[1,1,1],[0,1,0]], target = 0
输出：4
解释：四个只含 0 的 1x1 子矩阵。
```

**示例 2：**

```
输入：matrix = [[1,-1],[-1,1]], target = 0
输出：5
解释：两个 1x2 子矩阵，加上两个 2x1 子矩阵，再加上一个 2x2 子矩阵。
```

**示例 3：**

```
输入：matrix = [[904]], target = 0
输出：0
```

**提示：**

- `1 <= matrix.length <= 100`
- `1 <= matrix[0].length <= 100`
- `-1000 <= matrix[i] <= 1000`
- `-10^8 <= target <= 10^8`

## 解题思路

碰到这个题，我的第一反应就是二维前缀和，以 $prefix[i][j]$ 表示前 $i * j$ 个方块里的数字之和，那么子矩阵 $x_1，y_1，x_2，y_2$ 的数字之和为 $prefix[x_2 + 1][y_2 + 1] - prefix[x1][y_2 + 1] - prefix[x2 + 1][y1] + prefix[x1][y1]$，但是这样需要枚举 $x_1, y_1, x_2, y_2$，时间复杂度是 $O(n^4)$。

其实还有一种方案，可以转换为一维前缀和。我们可以先枚举矩形的左边界和右边界，当左边界和右边界都确定下来之后，就可以用一维前缀和的思路去解决了，当然，这里我们需要预先处理出每行数据的一维前缀和。时间复杂度为 $O(n^3)$。

[363. 矩形区域不超过 K 的最大数值和][link] (Hard) 的解法是类似的。

[link]: https://leetcode.cn/problems/max-sum-of-rectangle-no-larger-than-k/

## 代码

```cpp
class Solution {
  public:
    int numSubmatrixSumTarget(vector<vector<int>> &matrix, int target) {
        // 二维前缀和
        // 枚举左右边界，转换为一维前缀和
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> rowsum(m, vector<int>(n + 1));
        int res = 0;
        for (int i = 0; i < m; ++i) {
            for (int j = 1; j <= n; ++j) {
                rowsum[i][j] = rowsum[i][j - 1] + matrix[i][j - 1];
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j <= n; ++j) {
                // 左闭右开
                unordered_map<int, int> record;
                record[0] = 1;
                int sum = 0;
                for (int k = 1; k <= m; ++k) {
                    sum += rowsum[k - 1][j] - rowsum[k - 1][i];
                    if (record.find(sum - target) != record.end()) {
                        res += record[sum - target];
                    }
                    ++record[sum];
                }
            }
        }
        return res;
    }
};
```
