LeetCodeDiary

A Diary for solving LeetCode problems

View on GitHub

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'),那么这两个子矩阵也不同。

示例 1:

img

输入: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

提示:

数组 困难 动态规划

题解

事实上,我们能从「边」的角度来确定一个子矩阵,通过枚举三条边,然后加速找第四条边的过程,这样可以将复杂度降到 $O(n3)$。

这个分析思路在 (题解)363. 矩形区域不超过 K 的最大数值和 详细讲过,没有印象的同学可以翻翻。这道题一定程度上是那道题的简化版,在本题我们只需要找到矩阵和为 target 的值,因此只需要使用「哈希表」来记录出现过的值即可,而在 (原题)363. 矩形区域不超过 K 的最大数值和 中,我们需要找到和不超过 k 的最大子矩阵,因此还涉及「有序集合 + 二分」。

具体的,我们仍然需要先预处理出一个二维前缀和。然后通过枚举确定「子矩阵的上下边界」,在将「子矩阵右边界」不断右移的过程中,把「子矩阵右边界」到「原矩阵左边界」行程的矩阵和存入哈希表(因为要统计数量,存储格式为 {"面积”:"出现次数"} ),然后通过容斥原理来找到目标的「子矩阵左边界」。

假设当前我们「子矩阵的上下边界」已经固定,当枚举到某个「子矩阵右边界」时,我们先通过二维前缀和计算出「子矩阵右边界」和「原矩阵左边界」形成的矩阵和 cur,由于我们希望找到矩阵和为 target 的子矩阵,即希望找到一个「子矩阵左边界」使得矩阵和满足要求,这等价于从「哈希表」中找到一个 x,使得 cur - x = target,这是一个 $O(1)$ 操作。

image.png

代码

class Solution:
    def numSubmatrixSumTarget(self, matrix: List[List[int]], target: int) -> int:
        m, n = len(matrix), len(matrix[0])
        ans = 0
        for i in range(1, n + 1):
            presum = [0] * (m + 1)
            for j in range(i, n + 1):
                a = 0
                d = {0:1}
                for fixed in range(1, m + 1):
                    presum[fixed] += matrix[fixed-1][j-1]
                    a += presum[fixed]
                    if a - target in d:
                        ans += d[a - target]
                    if a in d:
                        d[a] += 1
                    else:
                        d[a] = 1
        return ans

Python暴力搜索会超时,卡在38例:38 / 40 个通过测试用例

class Solution:
    def numSubmatrixSumTarget(self, matrix: List[List[int]], target: int) -> int:
        m, n = len(matrix), len(matrix[0])
        cum = [[0] * (n + 1) for _ in range(m + 1)]
        for i in range(1,m+1):
            for j in range(1,n+1):
                cum[i][j] = cum[i-1][j] + cum[i][j-1] - cum[i-1][j-1] + matrix[i-1][j-1]
        ans = 0
        for x1 in range(1,m+1):
            for y1 in range(1,n+1):
                for x2 in range(x1,m+1):
                    for y2 in range(y1,n+1):
                        cur = cum[x2][y2] - cum[x1-1][y2] - cum[x2][y1-1] + cum[x1-1][y1-1]
                        if cur == target:
                            ans += 1
        return ans