最大正方形
题目描述
在一个 n × m n\times m n×m 的只包含 0 0 0 和 1 1 1 的矩阵里找出一个不包含 0 0 0 的最大正方形,输出边长。
输入格式
输入文件第一行为两个整数 n , m ( 1 ≤ n , m ≤ 100 ) n,m(1\leq n,m\leq 100) n,m(1≤n,m≤100),接下来 n n n 行,每行 m m m 个数字,用空格隔开, 0 0 0 或 1 1 1。
输出格式
一个整数,最大正方形的边长。
样例 #1
样例输入 #1
4 4
0 1 1 1
1 1 1 0
0 1 1 0
1 1 0 1
样例输出 #1
2
题解
这道题AcWing、洛谷和leetCode都有,只是输入还有输出的些微区别,这里只提供洛谷的Python代码,思路是一样的。
这道题其实不难看出来可以用动态规划做,但是我做这道题的时候是有人要求我先用前缀和做一遍了,所以我这里提供两种思路
1、前缀和
这道题前缀和做法其实很简单,就是看我们想要通过求的正方形的前缀和来求该正方形的面积,如果求出来的面积与正方形边长平方相等,那么这个边长的正方形就满足要求
if 通过前缀和求的面积 == 正方形边长 ** 2:
return True
怎么通过前缀和求矩形面积呢?我们可以通过下面公式来计算:
设
i
2
,
j
2
i_2, j_2
i2,j2 为矩形右下角,
i
1
,
j
1
=
i
2
−
l
e
n
S
q
u
a
r
e
+
1
,
j
2
−
l
e
n
S
q
u
a
r
e
+
1
i_1, j_1 = i_2 - lenSquare + 1, j_2 - lenSquare + 1
i1,j1=i2−lenSquare+1,j2−lenSquare+1 为矩形左上角,那么通过前缀和求矩形面积公式为:
S
i
z
e
(
S
q
u
a
r
e
)
=
P
r
e
f
i
x
[
i
2
]
[
j
2
]
−
P
r
e
f
i
x
[
i
1
−
1
]
[
j
2
]
−
P
r
e
f
i
x
[
i
2
]
[
j
1
−
1
]
+
P
r
e
f
i
x
[
i
1
−
1
]
[
j
1
−
1
]
Size(Square) =Prefix[i_2][j_2] -Prefix[i_1-1][j_2]-Prefix[i_2][j_1-1] +Prefix[i_1-1][j_1-1]
Size(Square)=Prefix[i2][j2]−Prefix[i1−1][j2]−Prefix[i2][j1−1]+Prefix[i1−1][j1−1]
下面这张图为上图的前缀和矩阵:
那么穷举求出每种正方形边长的情况,我们就可以得到可能的正方形边长
欸,别急,直接穷举正方形边长还是慢了,正方形边长是从小到大穷举的,我们可以使用二分来加速对边长的举证:
if mid正方边长满足要求:
我们去找是否存在更大的边长满足要求:left = mid + 1
else:
mid长度都不符合要求的,直接去找更小的边长了: right = mid - 1
最后得出Python代码(时间复杂度为 O ( N 2 l o g 2 N ) O(N^2log_2N) O(N2log2N)):
def judge(lenEdge, Prefix):
global N, M
for i in range(lenEdge, N+1):
for j in range(lenEdge, M+1):
if Prefix[i][j] - Prefix[i-lenEdge][j] - Prefix[i][j-lenEdge] + Prefix[i-lenEdge][j-lenEdge] == lenEdge**2:
return True
else:
return False
N, M = map(int, input().strip().split())
A = [[0 for _ in range(M+1)]]
for i in range(1, N+1):
tmp = [0]
tmp.extend(map(int, input().strip().split()))
A.append(tmp)
Prefix = [[0 for _ in range(M+1)] for _ in range(N+1)]
for i in range(1, N+1):
for j in range(1, M+1):
Prefix[i][j] = Prefix[i-1][j] + Prefix[i][j-1] - Prefix[i-1][j-1] + A[i][j]
left, right = 0, min(N, M)
ans = 0
while left <= right:
mid = (left + right) // 2
if judge(mid, Prefix):
ans = max(ans, mid)
left = mid + 1
else:
right = mid - 1
print(ans)
2、动态规划法
动态规划法的想法更容易想到,这里用图来说明一下:
定义
i
,
j
i,j
i,j为正方形的左下角坐标,且
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]存的是该正方形的边长
(
4
,
4
)
(4,4)
(4,4)代表的正方形的边长可以从红色、蓝色、绿色,(
(
3
,
3
)
,
(
3
,
4
)
,
(
4
,
3
)
(3,3),(3,4),(4,3)
(3,3),(3,4),(4,3))三种颜色的正方形来得出,
可以看出来,黑色框出正方形边长为1+1 = 2,通过多画图推导,得出下面的公式:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
−
1
]
)
+
1
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) + 1
dp[i][j]=min(dp[i−1][j],dp[i][j−1],dp[i−1][j−1])+1
时间复杂度为 O ( N 2 ) O(N^2) O(N2)
N, M = map(int, input().strip().split())
A = [[0 for _ in range(M)]] + [[0] + list(map(int, input().strip().split())) for _ in range(N)]
dp = [[0 for _ in range(M+1)] for _ in range(N+1)]
ans = 0
for i in range(1, N+1):
for j in range(1, M+1):
if A[i][j] == 1:
dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
ans = max(ans, dp[i][j])
print(ans)