大家好,今天我们一起做Leetcode第221题 ,这是一道难度为Medium的题目。也是一道经典的动态规划练习题
题目描述
Given a 2D binary matrix filled with 0's and 1's, find the largest square containing only 1's and return its area.
Example:
Input:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
Output: 4
题目的大意是,给你一个二维数组matrix,其中有一些是0,有一些是1,找到其中最大的由1组成的正方形,并且返回它的面积。
动态规划三部曲
老规矩,动态规划三部曲开 !
开数组
这一题比较有技巧的地方,是如何定义动态规划数组dp[i][j], 因为最后要求的是在matrix这个数组中能够形成最大的正方形的面积,很容易就把dp[i][j]定义成:从[0][0]到[i][j]的矩形中,其中由1组成的最大的正方形面积。那么在这个定义下,我们最后要输出的是dp[-1][-1]。
这个定义初看起来很有道理,但是有一个很大的问题。考虑如下的例子:
1 1 1 1
1 1 1 1
0 1 1 1
1 1 1 1
1 1 1 1
1 1 0 1
我们考虑从第三行三个元素到第三行第四个元素,在这个定义下这两个例子中,第三行第三个元素的dp值都为4,但是第一个例子中,第三行第四个元素的dp值为9,第二个例子中则为4。 这是因为0的位置有不同,导致了一个可以形成正方形,另外一个不可以。
所以以上dp的定义是不可以的,我们需要另外想一个办法。
我们可以把dp[i][j]定义为:从[0][0]到[i][j]形成的矩形中,以[i][j]为右下角顶点的正方形,边长的最大值。 揣摩一下它的定义。
我们举一个例子:
1 1 0 0
0 1 1 1
1 1 1 1
0 1 1 1
对应的,我们写出它的dp数组:
1 1 0 0
0 1 1 1
1 1 2 2
0 1 2 3
这样的话,我们最后输出的结果,应当是dp数组中的最大值,也就是matrix这个数组中最长的正方形边长。平方一下输出就是所求的结果。
知道了dp的定义以后,我们就照常开一个二维数组:
r = len(matrix)
c = len(matrix[0])
dp = [[0]*(c+1) for _ in range(r+1)]
状态转移方程
假设我们现在处理到了i,j这个位置,如果对应的matrix的元素为0,那么dp[i][j]直接等于0就可以了。因为右下角顶点是0,无论如何都不能组成全由1组成的正方形。
如果对应的matrix的元素为1,看一下下面这张图:
坐标(1,3)处的2意味着在这个位置有一个边长为2的正方形,同样,(1,2)和(2,2)也意味着这里有一个边长为2的长方形。那么为了形成一个边长为3的正方形,我们只需要在(2,3)位置处有1个1。所以(2,3)处的值为3.
接着看坐标(3,4)。(3,3)和(2,3)处的dp的值都是3,那么这两个点作为右下角,都可以形成边长为3的正方形。但是(2,4)处的dp值为1,这样,当(3,4)的值为1时,我们受最小的那个限制,也只能形成边长为2的正方形。
所以总结一下,当matrix对应元素为1时状态转移方程是:
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) + 1
找初始值
这一题很幸运的是,不需要额外进行初始值的寻找了。大家可以尝试一下把状态转移方程带入到边界中,恰好也可以用状态转移方程来算初始值。
实现
将以上三步结合一起来,要注意的是dp的index和matrix的index之间相差1,写程序的时候需要注意一下。另外最后我们需要找出dp数组中最大的数字,将它平方后就是matrix中最大的正方形的面积。我用的是max(map(max,dp)这种写法,比较简便。
可以直接在leetcode上提交的代码:
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
if not matrix:
return 0
r = len(matrix)
c = len(matrix[0])
dp = [[0]*(c+1) for _ in range(r+1)]
for i in range(1,r+1):
for j in range(1,c+1):
if matrix[i-1][j-1] == "1":
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) + 1
return max(map(max,dp))**2
这个解法大约能beat百分之九十的解法,应该算是在运行速度较快的解法中,最容易理解的一种了。
总结
这一题也是动态规划的经典习题,关键点在于如何定义dp数组。明天会继续带来更多的leetcode
支持
如果喜欢本文的话,欢迎关注我的微信公众号:老方刷题。谢谢您的支持!