LeetCode 85.最大矩形(穷举 + dp + 栈)

题目描述

问题描述


穷举法

无论拿到什么问题,首先试着用穷举法解答,这样可以有效的加深对题目的了解以及扩展思路。在本题中,我们可以模拟矩形的左上点坐标以及右下点左边,遍历所有情况就可以找出答案。虽然复杂度很高,但可以快速理解题意:

public int maximalRectangle1(char[][] matrix) {
    if (matrix.length == 0) {
        return 0;
    }
    int result = 0, height = matrix.length, length = matrix[0].length;
    // 遍历左上横坐标
    for (int i = 0; i < height; i++) {
    	// 遍历左上纵坐标
        for (int j = 0; j < length; j++) {
        	// 左上起码得满足条件
            if (matrix[i][j] == '1') {
				// 遍历右下横坐标
                for (int z = i; z < height; z++) {
                	// 遍历右下纵坐标
                    for (int x = j; x < length; x++) {
                    	// 右下起码得满足条件
                        if (matrix[z][x] == '1') {
                            boolean judge = true;
                            // 遍历左上到右下矩形中的所有节点
                            for (int q = i; q <= z; q++) {
                                for (int w = j; w <= x; w++) {
                                	// 如果有一个节点不满足就跳出循环
                                    if (matrix[q][w] == '0') {
                                        judge = false;
                                        break;
                                    }
                                }
                                if (!judge) {
                                    break;
                                }
                            }
                            if (judge) {
                            	// 计算满足条件的面积,找出最大值
                                int temp = (z - i + 1) * (x - j + 1);
                                result = Math.max(result, temp);
                            }
                        }
                    }
                }
            }
        }
    }
    return result;
}

dp

在上述穷举法中,我们通过遍历左上、右下节点的方式解决问题。但是循环的层数过多,现在我们试着只模拟右下节点。根据 dp,我们可以很容易算出某个节点左边连续一的个数和上面连续一的个数。这里我们控制行,根据列来计算。假设 dp[ i ][ j ] = 5,表示(i , j)左边左边连续1的个数为5,dp[ i - 1 ][ j ] = 4,表示(i-1 , j)节点左边连续1的个数为4,此时这两个节点组成的矩形面积就是 4 * 2 = 8。
根据这个思路,我们计算出所有节点左侧连续1的个数,按层遍历所有情况,找出最大值即可:

public int maximalRectangle2(char[][] matrix) {
    if (matrix.length == 0) {
        return 0;
    }
    int result = 0, height = matrix.length, length = matrix[0].length;
    // dp用来记录的数组
    int[][] record = new int[height][length];
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < length; j++) {
            if (j == 0) {
            	// 第一列初始化
                record[i][j] = matrix[i][j] == '1' ? 1 : 0;
            } else {
            	// 从第二列开始,如果当前节点是1,连续1的个数就是左侧节点值加一,否则为零
                record[i][j] = matrix[i][j] == '1' ? record[i][j - 1] + 1 : 0;
            }
            int temp = record[i][j];
            // 控制行,依次向上遍历
            for (int k = i; k >= 0; k--) {
            	// 宽度根据题意取最小的
                temp = Math.min(temp, record[k][j]);
                result = Math.max(result, temp * (i - k + 1));
            }
        }
    }
    return result;
}

单调栈解法

方法二动态规划中,我们把问题转换为控制长度,依次遍历高度的方式来解决。当然我们也可以控制高度,依次遍历行的问题来解决。此时问题就转换为:每轮循环给你一组数,分别代表当前节点上面有多少个连续一,求它能组成的最大矩形面积。此时问题就完全和上一篇博客,LeetCode 84 完全相同了,唯一的区别是本题中每轮循环都需要计算一次,找出其中最大值即可。

public int maximalRectangle3(char[][] matrix) {
    if (matrix.length == 0) {
        return 0;
    }
    int result = 0, height = matrix.length, length = matrix[0].length;
    // 二维dp实际可以优化为一维dp,因为它的节点值总和上一轮循环值唯一值有关
    int[] record = new int[length];
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < length; j++) {
            record[j] = matrix[i][j] == '1' ? record[j] + 1 : 0;
        }
        // 每轮循环都遍历一次,找出其中的最大值
        result = Math.max(result, getMaxValue(record));
    }
    return result;
}
// LeetCode 84 解法,这里我们做了优化
// leetCode 84 博客中,我们主要循环左右两边第一个小于当前列的下标
// 这里我们不记录左、右边界值,直接计算
private int getMaxValue(int[] record) {
    int result = 0, length = record.length;
    Stack<Integer> stack = new Stack<Integer>();
    // push -1 方便计算,判空也可以,不过比较麻烦
    stack.push(-1);
    for (int i = 0; i < length; i++) {
    	// 维护单调递增栈,寻找每个节点的左边界
        while (stack.peek() != -1 && record[i] <= record[stack.peek()]) {
        	// 出栈时说明,当前节点的右边已经找到
            result = Math.max(result, record[stack.pop()] * (i - stack.peek() - 1));
        }
        stack.push(i);
    }
    // 还在栈中的所有元素都没有右边界,左边界即栈中前一个元素位置
    while (stack.peek() != -1) {
        result = Math.max(result, record[stack.pop()] * (length - stack.peek() - 1));
    }
    return result;
}

dp

回头再看dp,既然我们上下,左右都可以进行dp,那么就可以模拟矩形中的任何一点,计算它的左边界、右边界、以及上下边界。按成遍历时可以遍历所有行,因此我们假设该节点处于矩阵的底层边,遍历所有情况就可以得到答案:

public int maximalRectangle4(char[][] matrix) {
    if (matrix.length == 0) {
        return 0;
    }
    int result = 0;
    int lengthX = matrix.length;
    int lengthY = matrix[0].length;
    // 记录左边起始下标
    int[] left = new int[lengthY];
    // 记录右边结束下标
    int[] right = new int[lengthY];
    // 记录顶部结束下标
    int[] height = new int[lengthY];
    // 默认右边在数组最右边,因此赋值即参数数组长度
    Arrays.fill(right, lengthY);
    for (int i = 0; i < lengthX; i++) {
        int temp_left = 0, temp_right = lengthY;
        // 高度计算逻辑不变
        for (int j = 0; j < lengthY; j++) {
            height[j] = matrix[i][j] == '1' ? height[j] + 1 : 0;
        }
        // 左边界取上层当前位置左边界和当前行较大的一个
        for (int j = 0; j < lengthY; j++) {
            if (matrix[i][j] == '1') {
                left[j] = Math.max(left[j], temp_left);
            } else {
            	// 这里赋0没关系,因为该节点高度是0,不会影响结果
            	// 下一层计算时也不会影响值
                left[j] = 0;
                temp_left = j + 1;
            }
        }
        // 右边和左边类似
        for (int j = lengthY - 1; j >= 0; j--) {
            if (matrix[i][j] == '1') {
                right[j] = Math.min(right[j], temp_right);
            } else {
                right[j] = lengthY;
                temp_right = j;
            }
        }
        // 遍历所有情况,找出最大面积
        for (int j = 0; j < lengthY; j++) {
            result = Math.max(result, (right[j] - left[j]) * height[j]);
        }
    }
    return result;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值