LeetCode 1504
方法1:单调栈
时间复杂度:O(mn),m、n分别是矩阵的行列数 想法:我当时没想到,参考的www.acwing.com/solution/co… 。这个思路跟之前的LeetCode 85转化为LeetCode 84有异曲同工之妙。就是说我指定某一行,往上看,看全部是1的矩形的最大高度是多少(这一步可以用一个很简单的dp实现),那就相当于说我有了一个数组,它就像个直方图一样,有一堆宽度为1,长度各异的矩形。之后再用单调栈来解。 如果我已经得到了v数组,它代表这个直方图,每个元素是高度,那么怎么求解全为1的子矩阵个数呢? 我们计算子矩形打算就按照一个一个矩形来计算。这句话听着像是废话但倒也不是,就是说假设有三个矩形,高度分别是3, 4, 2。那么计算这里面有多少个矩形的时候,我将把他们分成4比3高出来那一块,当成一个矩形,3最高的那一块和4的高度为3的那一块,合起来当成一个矩形,然后底下宽度为3,高度为2的矩形再当成另一个整的矩形。对于这里面每个矩形,我们知道长和宽,因此可以用组合数计算有多少个矩形。 为了实现这一点,单调栈每次弹出一个元素的时候,它肯定比弹完之后的栈顶和目前即将入栈的元素大,那么在这个时候,我们就先算它与它的两边的最高值比较,高出来那部分。单调栈放元素下标可以让我们既得到这个高出来的矩形的高度,又能得到宽度。 高度为h,宽度为w的矩形,总共用多少个子矩形?是h * C2w+1. 因为任何一个矩形的高度都在1到h之间,而宽度为w意味着底边是一条长为w的线段,分成长度为1的线段的话会有w条,有w+1个端点,任选两个与h组成矩形。高度只能在1到h之间取,因为当我们用单调栈的时候,每次我们的v数组是以当前的行为底的,我们不去计算那些底不在这条线上的矩形,因为那些矩形已经在之前被计算过了。
代码:
class Solution {
public int numSubmat(int[][] mat) {
int m = mat.length, n = mat[0].length;
int[] v = new int[n + 1];
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
v[j] = mat[i][j] == 0 ? 0 : v[j] + 1;
}
Stack<Integer> stack = new Stack<>();
for (int j = 0; j <= n; j++) {
while (!stack.isEmpty() && v[stack.peek()] >= v[j]) {
int index = stack.pop();
int h, w;
if (stack.isEmpty()) {
h = v[index] - v[j];
w = j;
}
else {
h = v[index] - Math.max(v[j], v[stack.peek()]);
w = j - stack.peek() - 1;
}
res += h * (w + 1) * w / 2;
}
stack.push(j);
}
}
return res;
}
}
这里的Stack如果用数组实现可以快很多(我写的运行4ms),但对于面试来说的话没什么差别,我就用库函数了。
方法2:DP
时间复杂度:O(m2n),m、n分别是矩阵的行列数 想法:我也没想到,很惭愧。方法来自www.youtube.com/watch?v=Eqz… 大意是开dp[m + 1][n + 1]数组,那么dp[i + 1][j + 1]代表,在原数组中[i, j]这个位置上,它左边总共积累了多少个1了。直接两重for循环,循环到一个点的时候,我可以往上找这一列,维护一个变量代表这一列的值,目前来说,往左数最少有多少个1,然后在res里加上。这个原则上来说是***O(m^2 * n)***的复杂度,但提交上去运行还蛮快的||=_=就很奇怪,但我个人觉得这个题还是掌握第一种做法比较好。
class Solution {
public int numSubmat(int[][] mat) {
int m = mat.length, n = mat[0].length;
int[][] dp = new int[m + 1][n + 1];
int res = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (mat[i - 1][j - 1] == 1) {
dp[i][j] = dp[i][j - 1] + 1;
int minOnes = dp[i][j];
for (int k = i - 1; k >= 0; k--) {
res += minOnes;
minOnes = Math.min(minOnes, dp[k][j]);
}
}
}
}
return res;
}
}
我这几天已经复习了一堆题了,但写题解远远赶不上直接复习的进度。目前我没有什么特别好的办法,或许只有当我写出来的时候才完全理解了,这才是到达真正理解所需要花的功夫?我可能会想一些办法来加快写题解的速度,但目前来说还是想继续执行这件事。