题目描述
给定一个只包含 0 和 1 的 rows * columns
矩阵 mat
,请你返回有多少个 子矩形 的元素全部都是 1。
样例
输入:mat = [[1,0,1],
[1,1,0],
[1,1,0]]
输出:13
解释:
有 6 个 1x1 的矩形。
有 2 个 1x2 的矩形。
有 3 个 2x1 的矩形。
有 1 个 2x2 的矩形。
有 1 个 3x1 的矩形。
矩形数目总共 = 6 + 2 + 3 + 1 + 1 = 13。
输入:mat = [[0,1,1,0],
[0,1,1,1],
[1,1,1,0]]
输出:24
解释:
有 8 个 1x1 的子矩形。
有 5 个 1x2 的子矩形。
有 2 个 1x3 的子矩形。
有 4 个 2x1 的子矩形。
有 2 个 2x2 的子矩形。
有 2 个 3x1 的子矩形。
有 1 个 3x2 的子矩形。
矩形数目总共 = 8 + 5 + 2 + 4 + 2 + 2 + 1 = 24。
输入:mat = [[1,1,1,1,1,1]]
输出:21
输入:mat = [[1,0,1],[0,1,0],[1,0,1]]
输出:5
限制
1 <= rows <= 150
1 <= columns <= 150
0 <= mat[i][j] <= 1
算法1
(暴力枚举) $O(n^3)$
- 枚举起始行 $i$,然后按顺序枚举终止行 $j$。对于第 $k$ 列,如果第 $i$ 行到第 $j$ 行都是 1,则令 $v_k := true$。否则 $v_k := false$。$v$ 数组可以动态维护。
- 固定 $i$ 和 $j$ 以及得到 $v$ 数组之后,统计以每一列结尾的矩形有多少个,即统计以每个 $k$ 结尾的最大连续的 $true$ 有多少,累计答案。
时间复杂度
- 枚举 $i$ 和 $j$ 需要 $O(n^2)$ 的时间,每次需要 $O(n)$ 的时间遍历每一列,故总时间复杂度为 $O(n^3)$。
空间复杂度
- 需要 $O(n)$ 的额外空间存储 $v$ 数组。
C++ 代码
class Solution {
public:
int numSubmat(vector<vector<int>>& mat) {
int r = mat.size(), c = mat[0].size();
int ans = 0;
for (int i = 0; i < r; i++) {
vector<bool> v(c, true);
for (int j = i; j < r; j++) {
for (int k = 0; k < c; k++)
if (mat[j][k] == 0)
v[k] = false;
for (int tot = 0, k = 0; k < c; k++) {
if (v[k] == false) {
tot = 0;
} else {
tot++;
ans += tot;
}
}
}
}
return ans;
}
};
算法2
(单调栈) $O(n^2)$
- 考虑一维的问题,假设给定了一个一维数组,每个元素的值代表当前位置的高度,求总共有多少个矩形。我们按行分解来解决这个问题。
- 我们按顺序遍历数组,然后维护一个严格单调递增的栈。如果当前高度 $v_j$ 小于等于 栈顶元素 $v_{top}$,则栈顶元素出栈。出栈后,$j$ 和新的栈顶元素 $st(top)$ 分别是 $top$ 两侧第一个严格比其低的位置。基于此,我们需要计算在 $top$ 比其两侧高的部分贡献的答案。然后当前位置 $j$ 进栈。
- 此时矩形的高度为 $h := v_{top} - \max{(v_j, v_{st(top)})}$,最大 宽度为 $w := j - st(top) - 1$。所有以 $top$ 为右端点的宽度都需要计算。所以贡献的答案为 $h * (w + 1) * w / 2$。
- 最后还在栈中的元素,按照假设最后还有一个高度为 0 的哨兵元素来进行出栈操作。这个一维问题的算法仅需要线性的时间。
- 此问题可以看做 $n$ 个一维问题,即记录下每一行中,每一列向上连续的 1 的最大高度 $v_j$。
时间复杂度
- 对于每一行,每个位置都仅进栈一次,出栈一次,故总时间复杂度为 $O(n^2)$。
空间复杂度
- 需要 $O(n)$ 的额外空间存储每一列的有效高度和单调栈。
C++ 代码
class Solution {
public:
int numSubmat(vector<vector<int>>& mat) {
int r = mat.size(), c = mat[0].size();
int ans = 0;
vector<int> v(c + 1, 0);
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++)
v[j] = mat[i][j] == 0 ? 0 : v[j] + 1;
stack<int> st;
for (int j = 0; j <= c; j++) {
while (!st.empty() && v[j] <= v[st.top()]) {
int top = st.top();
st.pop();
int h, w;
if (st.empty()) {
h = v[top] - v[j];
w = j;
} else {
h = v[top] - max(v[j], v[st.top()]);
w = j - st.top() - 1;
}
ans += h * (w + 1) * w / 2;
}
st.push(j);
}
}
return ans;
}
};