目录
统计全为 1 的子矩形数量 —— 二维矩阵子矩形计数问题解析
统计全为 1 的子矩形数量 —— 二维矩阵子矩形计数问题解析
题目描述
给定一个 m × n 的二进制矩阵 mat
,矩阵中的元素非 0 即 1。请你统计并返回所有元素全部为 1 的 子矩形(连续子矩阵)的数量。
什么是子矩形?
矩阵中的任意一个由连续的若干行和若干列组成的子区域都称为子矩形。
题目举例
例如,给定如下矩阵:
mat = [
[1, 0, 1],
[1, 1, 0],
[1, 1, 0]
]
满足全 1 的子矩形包括:
- 所有单个 1 的元素(共 6 个)
- 2×1 的子矩形(如第2行第1、2列的子矩形)
- 3×1 的子矩形(如第1列整列全是1)
- 等等……
最终统计所有满足条件的子矩形的个数。
解题分析
关键点
- 矩阵中元素只能是0或1,且子矩形中全部为1。
- 需要统计所有满足条件的子矩形数量,不是最大面积,而是数量。
直接思路
枚举矩阵中所有可能的子矩形,然后判断它们是否全部为1,时间复杂度极高(O(m² * n² * mn)),无法接受。
优化思路
要提高效率,需要利用动态规划或预处理思想:
- 预计算每个位置连续的1宽度
对矩阵的每一行,预计算每个元素左侧连续1的长度,记为width[i][j]
。
例如:
mat = [1, 1, 0, 1]
width = [1, 2, 0, 1]
- 利用width统计子矩形数量
枚举矩阵的每个元素(i, j)
作为子矩形的右下角,往上扩展行数,计算每行的最小连续1宽度min_width
,子矩形宽度不能超过这个值。
这样以 (i, j)
为右下角的所有全1子矩形数量,就是往上每一行宽度的最小值之和。
代码实现
class Solution:
def numSubmat(self, mat: List[List[int]]) -> int:
m, n = len(mat), len(mat[0])
# width[i][j]: 第 i 行第 j 列向左连续1的数量
width = [[0] * n for _ in range(m)]
# 计算连续1宽度
for i in range(m):
for j in range(n):
if mat[i][j] == 1:
width[i][j] = width[i][j-1] + 1 if j > 0 else 1
result = 0
# 以每个点为右下角,向上扩展统计子矩形
for i in range(m):
for j in range(n):
if mat[i][j] == 1:
min_width = width[i][j]
for k in range(i, -1, -1):
min_width = min(min_width, width[k][j])
if min_width == 0:
break
result += min_width
return result
代码详解
- 第一步:
width
数组的计算
通过遍历每一行元素,计算当前位置的连续1长度,方便后续快速判断子矩形宽度。 - 第二步:统计以
(i, j)
为右下角的子矩形数
以每个元素为右下角,向上逐行判断连续1的最小宽度,这个最小宽度即为该行能组成子矩形的最大宽度。累加即可得到该点贡献的子矩形数量。
复杂度分析
- 时间复杂度:
-
- 计算连续宽度:O(m * n)
- 枚举每个元素向上扩展:最坏情况为 O(m * n * m) = O(m² * n)
对于中等大小的矩阵(几百行列)通常能接受。
- 空间复杂度:O(m * n)
需要额外空间存储宽度信息。
示例说明
以如下矩阵为例:
mat = [
[1, 1, 0],
[1, 1, 1]
]
width
计算结果为:
width = [
[1, 2, 0],
[1, 2, 1]
]
- 对
(1, 2)
位置(第2行第3列,值为1)向上扩展:
-
- 第2行宽度 = 1
- 第1行宽度 = 0 (无法组成更大的子矩形)
总计贡献 1 个子矩形。
- 对
(1, 1)
位置:
-
- 第2行宽度 = 2
- 第1行宽度 = 2 (取两行的最小值2)
所以以(1, 1)
为右下角,可以组成 2 + 2 = 4 个子矩形。
依次计算,累加所有元素的贡献,得到总数。
方案比较
- 暴力枚举:时间复杂度极高,不可取。
- 动态规划 + 预处理宽度(本解法):时间复杂度适中,思路直观,容易理解。
- 单调栈优化(可以针对更复杂版本,如最大矩形问题):不一定适合本题统计所有子矩形数目。
总结
本题利用了「预处理连续宽度 + 向上扩展统计」的巧妙方法,大大减少了重复计算,达到了可接受的效率。此思路在类似「二维矩阵中计算满足某条件的子矩形数目」问题中也十分有用。