题目
在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
示例:
输入:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
输出: 4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximal-square
解题
思路
- 可以把这个二维矩阵问题想象成直方图问题,就和很多已做过的柱状图问题类似了。把值为1的想象为柱状图。如示例二维矩阵可想象为如下:
- 求最大正方形的面积可以从求能构成最大正方形的最大边长问题入手。
暴力解法
从左上角元素matrix[0][0]开始遍历,依次遍历矩阵中的每个元素。计算到这个元素时,将该元素作为正方形左上角时,可构成的最大正方形的最长边长maxSide。遍历完所有元素后最大的maxSide就是所求最大正方形的边长。所以对于每个元素来说有两种情况:
- 该元素matrix[i][j]为0时,无法作为正方形的左上角,不考虑,继续遍历下一个元素。
- 该元素matrix[i][j]为1时,可以作为正方形的左上角。从它所在的行和列开始扩展。每次往下扩展一行and往后扩展一列,判断新增的那一行and列中的值是否都为1,如果都为1可以继续扩展,如果存在为0的,这一行/列就不能被扩展了,扩展结束,记录当前最大边长。
class Solution {
public int maximalSquare(char[][] matrix) {
//暴力法,枚举每个位置作为正方形左上角可构成的最大正方形的最大边长
int maxSide = 0;//最大边长
//判断特殊情况
if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return 0;
int rows = matrix.length;
int cols = matrix[0].length;
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
if(matrix[i][j] == '1'){//如果找到一个可作为正方形左上角的元素
maxSide = Math.max(1,maxSide);
int r = i, c = j;//扩展的行/列索引
boolean flag = true;//默认可以扩展边长
while(flag == true && r+1<rows && c+1<cols && matrix[r+1][c]=='1' && matrix[r][c+1]=='1'){
r++;
c++;
int curSide = r-i+1;//假设扩展的当前边长
for(int k = 0;k < curSide; k++){
if(matrix[r][j+k] == '0' || matrix[i+k][c] == '0'){//判断扩展的那一行/列中存在0
curSide--;//这行/列不能扩展,假设的扩展边长撤回
flag = false;//并且标识为当前不能再扩展边长了
break;
}
}
maxSide = Math.max(curSide,maxSide);
}
}
}
}
return maxSide*maxSide;
}
}
动态规划
从左上角元素matrix[0][0]开始遍历,依次遍历矩阵中的每个元素,用递推的方法计算到这个元素时(如matrix[i][j],下图标红元素matrix[2][4]),该元素作为正方形右下角(如下图红框正方形)时可构成的最大正方形的最长边长dp[i][j]。所求二维矩阵内最大的正方形的边长就是依次遍历可得的dp[i][j]中的最大值。
同样对于每个元素有两种情况:
- 当这个元素matrix[i][j]为0 时,不能作为正方形的右下角,不做考虑,dp[i][j] = 0。
- 当这个元素matrix[i][j]为1 时,可以当作正方形的右下角,可以从三种情况递推而来(或的关系):
① 从该元素上边元素递推而来,若上边元素为1,假设可以加上该元素边长+1扩展正方形;若上边元素为0,则以上边元素为右下角的正方形不存在,dp[i-1][j] =0,该元素作为右下角只能重新从自己开始,dp[i][j] = 0+1。
比如对matrix[2][4]来说,从dp[1][4] (如下图黄色框)递推而来假设形成的正方形为如下图粗线红框。这为一种情况:
dp[i][j] = dp[i-1][j]+1;
② 从该元素左边元素递推而来,若左边元素为1,假设可以加上该元素边长+1扩展正方形;若左边元素为0,则以左边元素为右下角的正方形不存在,dp[i][j-1] =0,该元素作为右下角只能重新从自己开始,dp[i][j] = 0+1。
比如对matrix[2][4]来说,从dp[2][3] (如下图黄色框)递推而来假设形成的正方形为如下图粗线红框。这为一种情况:
dp[i][j] = dp[i][j-1]+1;
如图所示,扩展的正方形的扩展元素(红色圈的元素)中包含有为0的,表明这个扩展正方形不存在,扩展为0元素的那一列/行其实会被其他两种情况(从左边和从上边扩展递推来的)排除掉那一列/行,这也就是我们为什么要从三种情况中取最小的原因,木桶短板原理。
③ 从该元素左上角元素递推而来,若左上角元素为1,假设可以加上该元素边长+1扩展正方形;若左上角元素为0,则以左上角元素为右下角的正方形不存在,dp[i-1][j-1] =0,该元素作为右下角只能重新从自己开始,dp[i][j] = 0+1。
比如对matrix[2][4]来说,从dp[1][3] (如下图黄色框)递推而来假设形成的正方形为如下图粗线红框。这为一种情况::
dp[i][j] = dp[i-1][j-1]+1;
这三种情况选择结果最小的一个(交集):
dp[i][j] = min(dp[i][j] = dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1)
因为如果想要扩展边长+1,必须保证该元素从左、上、左上三个方向扩展来的正方形的扩展元素(如上示例图中红色圈住的元素)都为1,这个扩展正方形才存在。
取三种情况的最小相当于选择三个扩展正方形的交集,保证了扩展后的正方形内的元素都为1。因为如果扩展后的正方形的未知扩展元素不是1,会被其他两个方向的扩展正方形排除掉那一列/行从而其他两个扩展正方形不包含那个元素,边长更小。
边界条件:
第一行和第一列的元素来说,如果要作为正方形右下角,他们的边长最长为1(因为只有一行/一列)。所以给第一行和第一列的元素值为1的dp[i][0]和dp[0][j]赋值为1,其他为0。
class Solution {
public int maximalSquare(char[][] matrix) {
int maxSide = 0;//维护最大正方形的边长
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return 0;
}
int rows = matrix.length;
int cols = matrix[0].length;
int[][] dp = new int[rows][cols];//第i行j列元素能作为正方形右下角的最大正方形的边长
for(int i = 0; i < rows; i++){//一行一行加
for(int j = 0; j < cols; j++){//对每一行来说,一列一列加
if(matrix[i][j] == '1'){
if(i == 0 || j == 0){//边界条件,第一行和第一列元素的位置为1时,dp[0][0] = 1
dp[i][j] = 1;
}else{
dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]), dp[i-1][j-1])+1;
}
maxSide = Math.max(maxSide,dp[i][j]);
}
}
}
int res = maxSide*maxSide;
return res;
}
}
优化: 用原数组matrix[][]代替dp[][]数组存储最长边长,不需另外创建一个二维数组。因为matrix[][]是char类型的,边长是int,只需要注意两种类型的转换即可。按理说优化了空间复杂度,但不知道为什么内存消耗并没有减少,但还是学学吧,代码如下(参考官方题解下的评论):
class Solution {
public int maximalSquare(char[][] matrix) {
//用原数组直接代替dp数组存储 注意char类型和int的转换
char maxSide = '0';//维护最大正方形的边长
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return 0;
}
int rows = matrix.length;
int cols = matrix[0].length;
for(int i = 0; i < rows; i++){//一行一行加
for(int j = 0; j < cols; j++){//对每一行来说,一列一列加
if(matrix[i][j] == '1'){
if(i == 0 || j == 0){
if(maxSide == '0') maxSide = '1';
}else if(matrix[i][j]>'0'){
matrix[i][j] = matrix[i-1][j-1] > matrix[i-1][j]?matrix[i-1][j]:matrix[i-1][j-1];
matrix[i][j] = matrix[i][j] > matrix[i][j-1]?matrix[i][j-1]:matrix[i][j];
matrix[i][j]++;
maxSide = maxSide > matrix[i][j]?maxSide:matrix[i][j];
}
}
}
}
return (maxSide-'0')*(maxSide-'0');
}
}
参考:力扣官方题解