【LeetCode 每日一题】1504. 统计全 1 子矩形
Problem: 1504. 统计全 1 子矩形
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(m² * n)
- 空间复杂度:O(n)
整体思路
这段代码的目的是计算一个二维01矩阵 mat
中,所有仅由 1 组成的子矩阵的总数量。
该算法采用了一种非常巧妙的 降维打击 策略,也称为 “固定边界法”。它通过枚举所有可能的子矩阵的 上下边界,将一个二维的矩阵计数问题,转化为多个一维的数组计数问题。
-
核心思想:固定上下边界,转化为一维问题
- 算法通过两层外层循环,
top
和bottom
,来固定所有可能的水平“切片”(即子矩阵的上下边界)。 - 对于每一个固定的
top
和bottom
,问题就变成了:在这个由top
行和bottom
行界定的、高度为h = bottom - top + 1
的子矩阵中,有多少个全为 1 的矩形? - 为了解决这个子问题,算法进一步将这个切片“压扁”成一个一维数组。
- 算法通过两层外层循环,
-
“压扁”操作与一维数组的含义
- 对于每个固定的
[top, bottom]
切片,算法会动态构建一个一维数组a
。 a[i]
的含义是:在第i
列,从top
行到bottom
行,有多少个 1。- 代码通过
a[i] += mat[bottom][i]
巧妙地实现了这个累加。当bottom
指针下移时,a
数组会不断更新,反映新切片中每一列的 1 的数量。 - 如果
a[i]
的值恰好等于当前切片的高度h
,这就意味着在第i
列,从top
到bottom
的所有元素都是 1。我们可以将这样的列视为一个“有效列”。
- 对于每个固定的
-
解决一维问题:统计连续“有效列”构成的子数组
- 当我们将二维切片转化为一个由“有效列”(值为
h
)和“无效列”(值不为h
)组成的一维序列后,原问题就变成了:在这个一维序列中,统计所有由“有效列”组成的连续子数组的数量。 - 这与
zeroFilledSubarray
问题(统计全0子数组)的逻辑是完全一样的。 - 代码使用
last
变量来记录上一个“无效列”的索引。 - 当遇到一个“有效列”
i
时,i - last
的值就等于当前连续“有效列”块的长度,这也恰好是以当前第i
列为结束列的所有新的全 1 矩形的数量。将这个值累加到ans
中。
- 当我们将二维切片转化为一个由“有效列”(值为
总结流程:枚举所有 top
-> 枚举所有 bottom
-> 形成一个水平切片 -> 将切片问题转化为一维的“有效列”问题 -> 在一维上统计连续子数组数量 -> 累加到总结果。
完整代码
class Solution {/*** 计算一个二维01矩阵中,所有元素都为 1 的子矩阵的总数量。* @param mat 一个由 0 和 1 组成的二维整数数组* @return 全为 1 的子矩阵的总数*/public int numSubmat(int[][] mat) {int m = mat.length;int n = mat[0].length;int ans = 0;// 外层循环:固定子矩阵的上边界 topfor (int top = 0; top < m; top++) {// a: 一维数组,用于“压扁”二维子矩阵。// a[i] 将存储在当前 [top, bottom] 切片中,第 i 列的 1 的总数。int[] a = new int[n];// 中层循环:固定子矩阵的下边界 bottom,从 top 开始向下扩展for (int bottom = top; bottom < m; bottom++) {// last: 记录上一个“无效列”的索引。初始化为-1。int last = -1;// h: 当前 [top, bottom] 切片的高度。int h = bottom - top + 1;// 内层循环:遍历当前切片的每一列for (int i = 0; i < n; i++) {// 更新列的和:将 bottom 行的新元素加入a[i] += mat[bottom][i];// 判断当前列是否为“有效列”// 如果 a[i] 不等于切片高度 h,说明该列在 [top, bottom] 之间存在 0if (a[i] != h) {// 这是一个无效列,更新 last 的位置last = i;} else {// 这是一个有效列。// i - last 的值等于当前连续有效列的长度。// 这个长度也等于以当前第 i 列为结束列的、新的全1矩形的数量。ans += i - last;}}}}return ans;}
}
时空复杂度
时间复杂度:O(m² * n)
- 外层循环 (
top
):执行m
次。 - 中层循环 (
bottom
):对于每个top
,bottom
从top
运行到m-1
。总的迭代次数是m + (m-1) + ... + 1
,这是一个 O(m²) 的级别的操作。 - 内层循环 (
i
):对于每一对(top, bottom)
,i
循环执行n
次。
综合分析:
算法的总操作次数大致是 (m * m / 2) * n
。因此,其时间复杂度为 O(m² * n)。
空间复杂度:O(n)
- 主要存储开销:算法在
top
循环的每次迭代中,都会创建一个名为a
的整型数组。 - 空间大小:数组
a
的长度是n
,即矩阵的列数。这个数组在下一次top
循环开始时会被重新创建,所以空间不会累积。 - 其他变量:
m
,n
,ans
,top
,bottom
,last
,h
,i
等都只占用常数级别的空间。
综合分析:
算法在任何时刻所需的额外辅助空间主要由数组 a
决定。因此,其空间复杂度为 O(n)。
参考灵神