题目:1139. 最大的以 1 为边界的正方形
题目描述:
给你一个由若干 0 和 1 组成的二维网格 grid,请你找出边界全部由 1 组成的最大 正方形 子网格,并返回该子网格中的元素数量。如果不存在,则返回 0。
示例:
输入:`grid = [[1,1,1],[1,0,1],[1,1,1]]`
输出:`9`
提示:
(1)`1 <= grid.length <= 100`
(2)`1 <= grid[0].length <= 100`
(3)`grid[i][j] 为 0 或 1`
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
(1)暴力法
当实在没有更好的办法时,暴力法也可以试一试。
思路
从大到小,遍历所有可能的正方形,看是否满足条件,第一个满足条件的正方形即符合要求,返回边长的平方即可。
代码:
class Solution:
def largest1BorderedSquare(self, grid) -> int:
r, c = len(grid), len(grid[0])
# 这里是为了防止给出的grid长和宽不相等
def check(x1, y1, x2, y2):
# 检验选中的正方形是否满足题目要求
# 其中输入条件是正方形左上角和右下角的后一个点的点的坐标(即正方形右下角坐标应该是(x2-1,y2-1))
for x in range(x1, x2):
if grid[x][y1] != 1 or grid[x][y2-1] != 1: # 边框值是否为1
return False
for y in range(y1, y2):
if grid[x1][y] != 1 or grid[x2-1][y] != 1: # 边框值是否为1
return False
return True
for k in range(min(r, c), 0, -1):
for x1 in range(r):
if x1 + k > r: # 正方形越界,退出
break
for y1 in range(c):
x2, y2 = x1 + k, y1 + k # 得到正方形右下角的下一个点坐标
if y2 > c: # 正方形越界,退出
break
if check(x1, y1, x2, y2): # 检查得到的正方形是否满足条件
return k ** 2 # 若满足条件则返回面积
return 0 # 没有找到符合条件的正方形,返回0
注意:
(1)由于是从最大正方形开始遍历,找到的符合题目要求的第一个正方形即为满足条件的最大正方形
(2)暴力法的时间复杂度为O(n^4)(n是输入边长)
.
(3)这里考虑了输入长宽不相等的情况,若题目给定的grid
是正方形,可不考虑。
(2)动态规划
思路:
(1)建立dp数组。
三维数组dp第一和第二维分别比原数组的长度大1,便于处理边界。第三维用两个数分别记录给定数组在当前元素位置时,左边(含本身)和上方(含本身)连续的1的个数。
(2)遍历dp数组。
记录当前元素左边和上边连续1的个数的最小值作为边长的可能值,若当前边长可能值比目前输出中存储的边长要大,则检查另外两条边在当前边长可能值下是否满足条件。若满足条件则更新输出,否则减小当前边长可能值的大小,若减一后的边长可能值仍然大于目前输出的边长,继续判断另外两条边。。。
(3)第二步的遍历也是从大到小,满足条件的第一个正方形即为满足要求的最大正方形。
代码:
class Solution:
def largest1BorderedSquare(self, grid) -> int:
r,c = len(grid), len(grid[0])
dp = [[[0,0] for j in range(c+1)] for i in range (r+1)]
# dp数组初始化
for i in range(r):
for j in range(c):
if grid[i][j] == 1:
# 用一个三维矩阵记录原矩阵每个元素的左边和上边(包括)有多少个连续的1
# 其中矩阵的维度比原始矩阵大1,可以省略边界条件判断(j+1可能越界)
dp[i+1][j+1][0] = 1 + dp[i+1][j][0] # 左边连续的1
dp[i+1][j+1][1] = 1 + dp[i][j+1][1] # 上边连续的1
ans = 0
for i in range(1,r+1):
for j in range(1,c+1): # 遍历记录矩阵
height = min(dp[i][j]) # 记录当前元素左边和上边连续1的个数的最小值作为边长的可能值
while height > ans:
if min(dp[i][j][0],dp[i][j][1],dp[i-height+1][j][0],dp[i][j-height+1][1]) >= height:
# 检查另外两条边长是否符合要求
# 若符合要求更新边长
ans = height
break
else:
# 若另两条边长不满足最大边长,求出另外两条边长最大的连续长度
height -= 1
return ans*ans
注意:
(1)dp矩阵的维度比原始矩阵大1,可以省略边界条件判断(i+1和j+1可能越界)
(2)关于检查另外两条边:
假设当前可能边长为height
,当前输出边长为ans
,遍历dp数组的位置在[i,j]
,即min(dp[i][j])=height
,则要知道边长为height
的正方形是否满足条件,需要判断min(dp[i][j+k-1])
是否大于ans,若不满足,还不能立即退出,需要在满足可能边长大于当前输出边长的情况下,看能否找到比当前输出边长更大的正方形,也就是这里需要一个循环对应代码中:
while height > ans:
if min(dp[i][j][0],dp[i][j][1],dp[i-height+1][j][0],dp[i][j-height+1][1]) >= height:
# 检查另外两条边长是否符合要求
# 若符合要求更新边长
ans = height
break
else:
# 若另两条边长不满足最大边长,求出另外两条边长最大的连续长度
height -= 1
(3)因为当前可能边长height
是会改变的故在循环中的判断语句中需要判断dp数组中的四个点,不能省略前两个。即:
if min(dp[i][j][0],dp[i][j][1],dp[i-height+1][j][0],dp[i][j-height+1][1]) >= height: