本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
给你一个 m * n
的矩阵,矩阵中的元素不是 0
就是 1
,请你统计并返回其中完全由 1
组成的 正方形 子矩阵的个数。
示例 1:
输入:matrix =
[
[0,1,1,1],
[1,1,1,1],
[0,1,1,1]
]
输出:15
解释:
边长为 1 的正方形有 10 个。
边长为 2 的正方形有 4 个。
边长为 3 的正方形有 1 个。
正方形的总数 = 10 + 4 + 1 = 15.
示例 2:
输入:matrix =
[
[1,0,1],
[1,1,0],
[1,1,0]
]
输出:7
解释:
边长为 1 的正方形有 6 个。
边长为 2 的正方形有 1 个。
正方形的总数 = 6 + 1 = 7.
提示:
1 <= arr.length <= 300
1 <= arr[0].length <= 300
0 <= arr[i][j] <= 1
解法 动态规划/递推(最优)
本题和 221. 最大正方形 非常类似,使用的方法也几乎相同。
我们用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以 ( i , j ) (i,j) (i,j) 为右下角的正方形的最大边长,那么除此定义之外, d p [ i ] [ j ] = x dp[i][j] = x dp[i][j]=x 也表示以 ( i , j ) (i,j) (i,j) 为右下角的正方形的数目为 x x x(即边长为 1 , 2 , . . . , x 1, 2, ..., x 1,2,...,x 的正方形各一个)。在计算出所有的 d p [ i ] [ j ] dp[i][j] dp[i][j] 后,我们将它们进行累加,就可以得到矩阵中正方形的数目。
我们尝试挖掘
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 与相邻位置的关系来计算出
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 的值。
如上图所示,若对于位置
(
i
,
j
)
(i,j)
(i,j) 有
d
p
[
i
]
[
j
]
=
4
dp[i][j] = 4
dp[i][j]=4 ,我们将以
(
i
,
j
)
(i,j)
(i,j) 为右下角、边长为
4
4
4 的正方形涂上色,可以发现其左侧位置
(
i
,
j
−
1
)
(i, j - 1)
(i,j−1) ,上方位置
(
i
−
1
,
j
)
(i - 1, j)
(i−1,j) 和左上位置
(
i
−
1
,
j
−
1
)
(i - 1, j - 1)
(i−1,j−1) 均可以作为一个边长为
4
−
1
=
3
4 - 1 = 3
4−1=3 的正方形的右下角。也就是说,这些位置的的
d
p
dp
dp 值至少为
3
3
3 ,即:
dp[i][j - 1] >= dp[i][j] - 1
dp[i - 1][j] >= dp[i][j] - 1
dp[i - 1][j - 1] >= dp[i][j] - 1
将这三个不等式联立,可以得到:
min
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
1
]
)
≥
d
p
[
i
]
[
j
]
−
1
\min\big(dp[i][j - 1],\ dp[i - 1][j],\ dp[i - 1][j - 1]\big) \geq dp[i][j] - 1
min(dp[i][j−1], dp[i−1][j], dp[i−1][j−1])≥dp[i][j]−1
这是我们通过固定 d p [ i ] [ j ] dp[i][j] dp[i][j] 的值,判断其相邻位置与之的关系得到的不等式。同理,我们也可以固定 d p [ i ] [ j ] dp[i][j] dp[i][j] 相邻位置的值,得到另外的限制条件。
如上图所示,假设
d
p
[
i
]
[
j
−
1
]
dp[i][j - 1]
dp[i][j−1] ,
d
p
[
i
−
1
]
[
j
]
dp[i - 1][j]
dp[i−1][j] 和
d
p
[
i
−
1
]
[
j
−
1
]
dp[i - 1][j - 1]
dp[i−1][j−1] 中的最小值为
3
3
3 ,也就是说,
(
i
,
j
−
1
)
(i, j - 1)
(i,j−1) ,
(
i
−
1
,
j
)
(i - 1, j)
(i−1,j) 和
(
i
−
1
,
j
−
1
)
(i - 1, j - 1)
(i−1,j−1) 均可以作为一个边长为
3
3
3 的正方形的右下角。我们将这些边长为
3
3
3 的正方形依次涂上色,可以发现,如果位置
(
i
,
j
)
(i,j)
(i,j) 的元素为
1
1
1 ,那么它可以作为一个边长为
4
4
4 的正方形的右下角,
d
p
dp
dp 值至少为
4
4
4 ,即:
d
p
[
i
]
[
j
]
≥
min
(
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
1
]
)
+
1
dp[i][j] \geq \min\big(f[i][j - 1], f[i - 1][j], f[i - 1][j - 1]\big) + 1
dp[i][j]≥min(f[i][j−1],f[i−1][j],f[i−1][j−1])+1
将其与上一个不等式联立,可以得到:
d
p
[
i
]
[
j
]
=
min
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
1
]
)
+
1
dp[i][j] = \min\big(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]\big) + 1
dp[i][j]=min(dp[i][j−1],dp[i−1][j],dp[i−1][j−1])+1
这样我们就得到了
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 的递推式。此外还要考虑边界(
i
=
0
i = 0
i=0 或
j
=
0
j = 0
j=0)以及位置
(
i
,
j
)
(i,j)
(i,j) 的元素为
0
0
0 的情况。
我们按照行优先的顺序依次计算 d p [ i ] [ j ] dp[i][j] dp[i][j] 的值,就可以得到最终的答案。
class Solution {
public:
int countSquares(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
int ans = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == 1) {
dp[i + 1][j + 1] = 1 +
min(dp[i][j],
min(dp[i][j + 1], dp[i + 1][j]));
ans += dp[i + 1][j + 1];
}
}
}
return ans;
}
};
由于递推式中 d p [ i ] [ j ] dp[i][j] dp[i][j] 只与本行和上一行的若干个值有关,因此空间复杂度可以优化至 O ( N ) O(N) O(N) 。
class Solution {
public:
int countSquares(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<int> dp(n + 1);
int ans = 0;
int pre = 0, temp = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == 1) {
temp = dp[j + 1];
dp[j + 1] = 1 +
min(pre,
min(dp[j + 1], dp[j]));
pre = temp; // pre为dp[i][j]
ans += dp[j + 1];
} else pre = dp[j + 1], dp[j + 1] = 0; // 注意此时也要记录dp[i][j],并更新dp[i+1][j+1]
}
}
return ans;
}
};
复杂度分析:
- 时间复杂度: O ( m n ) O(mn) O(mn)
- 空间复杂度: O ( n ) O(n) O(n)