【算法】单调栈题单——矩阵系列⭐

题目列表

84. 柱状图中最大的矩形(单调栈找左右两边第一个更低的位置)

https://leetcode.cn/problems/largest-rectangle-in-histogram/description/

在这里插入图片描述

提示:
1 <= heights.length <=10^5
0 <= heights[i] <= 10^4

以 heights[i] 为高度的矩形,它的宽是* (r[i] - l[i] - 1);
其中 r[i] 和 l[i] 为左右两边第一个更低的位置。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        // 记录左右第一个比它小的下标
        int[] l = new int[n], r = new int[n];
        Arrays.fill(l, -1);     // 左边第一个大于heights[i]
        Arrays.fill(r, n);      // 右边第一个小于等于heights[i]
        Deque<Integer> stk = new ArrayDeque<>();
        for (int i = 0; i < n; ++i) {
            while (!stk.isEmpty() && heights[i] <= heights[stk.peek()]) {
                r[stk.pop()] = i;
            }
            if (!stk.isEmpty()) l[i] = stk.peek();
            stk.push(i);
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            int s = heights[i] * (r[i] - l[i] - 1);
            if (s > ans) ans = s;
        }
        return ans;
    }
}

从具体结果来看,r[i] 表示的是小于等于的而不是小于的,这看起来会出错但其实并不会。
原因在于,如果h[i]和h[j]是相同的高度,那么r[i]=j这是错误的,但是r[j]可以得出正确的答案。
具体看下面的例子:
image.png

85. 最大矩形⭐⭐⭐⭐⭐

https://leetcode.cn/problems/maximal-rectangle/description/

在这里插入图片描述

提示:
rows == matrix.length
cols == matrix[0].length
1 <= row, cols <= 200
matrix[i][j] 为 '0' 或 '1'

解法1——使用柱状图的优化暴力方法

https://leetcode.cn/problems/maximal-rectangle/solutions/535672/zui-da-ju-xing-by-leetcode-solution-bjlu/

在这里插入图片描述
在这里插入图片描述


总结一下思路:

  1. 预处理出 left[i][j],是每个点作为右下角时,它的左边包括多少连续的1。
  2. 枚举每个点作为右下角,计算其最大面积。随着高度的变大,宽度会随之减小。
class Solution {
    public int maximalRectangle(char[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        // 构造 left 数组
        int[][] left = new int[m][n];
        for (int i = 0; i < m; ++i) {
            if (matrix[i][0] == '1') left[i][0] = 1;
            for (int j = 1; j < n; ++j) {
                if (matrix[i][j] == '1') {
                    left[i][j] = left[i][j - 1] + 1;
                }
            }
        }

        int ans = 0;
        // 枚举每个点(i,j)作为右下角
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                int width = left[i][j], area = width * 1;
                // 尝试扩展高度
                for (int k = i - 1; k >= 0; --k) {
                    width = Math.min(width, left[k][j]);
                    area = Math.max(area, width * (i - k + 1));
                }
                ans = Math.max(ans, area);
            }
        }
        return ans;
    }
}

解法2——单调栈 :归因到 84. 柱状图中最大的矩形 🐂

解法1 需要枚举每个点,对于每个点又有 m 的复杂度,所以时间复杂度是 O ( m 2 ∗ n ) O(m^2*n) O(m2n)

可以优化成求解每一行 m,对于每一行,相当于 84. 柱状图中最大的矩形 的时间复杂度,总的复杂度是 O ( m ∗ n ) O(m*n) O(mn)

在这里插入图片描述

class Solution {
    public int maximalRectangle(char[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        int[] h = new int[n];
        int[] l = new int[n], r = new int[n];
        int ans = 0;
        for (int i = 0; i < m; ++i) {
            // 处理以第 i 行为底的矩形
            for (int j = 0; j < n; ++j) {
                if (matrix[i][j] == '1') h[j]++;
                else h[j] = 0;
            }
            // 利用单调栈求解
            Deque<Integer> stk = new ArrayDeque<>();
            Arrays.fill(l, -1);
            Arrays.fill(r, n);
            for (int j = 0; j < n; ++j) {
                while (!stk.isEmpty() && h[j] < h[stk.peek()]) {
                    r[stk.pop()] = j;
                }
                if (!stk.isEmpty()) l[j] = stk.peek();
                stk.push(j);
            }
            int area = 0;
            for (int j = 0; j < n; ++j) {
                area = Math.max(area, h[j] * (r[j] - l[j] - 1));
            }
            if (area > ans) ans = area;
        }
        return ans;
    }
}

1504. 统计全 1 子矩形⭐

https://leetcode.cn/problems/count-submatrices-with-all-ones/description/

在这里插入图片描述

提示:
1 <= m, n <= 150
mat[i][j] 仅包含 0 或 1

解法1——枚举 O ( m 2 ∗ n ) O(m^2*n) O(m2n)

class Solution {
    public int numSubmat(int[][] mat) {
        int m = mat.length, n = mat[0].length, ans = 0;
        int[][] left = new int[m + 1][n + 1];
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (mat[i - 1][j - 1] == 1) {
                    left[i][j] = left[i][j - 1] + 1;
                }

                int w = left[i][j];
                for (int k = i; k >= 1 && w > 0; --k) {
                    w = Math.min(w, left[k][j]);
                    ans += w;	// 每个高度有w个宽度可选
                }
            }
        }
        return ans;
    }
}

解法2——单调栈优化⭐⭐⭐⭐⭐(比较难理解,细细品味)

配合官解食用,https://leetcode.cn/problems/count-submatrices-with-all-ones/solutions/336410/tong-ji-quan-1-zi-ju-xing-by-leetcode-solution/

在这里插入图片描述
在这里插入图片描述

为了复用上面的结果,需要用单调栈维护一个单调非递减的序列,只有这样才能满足:该层的 sum 是上层的 sum + 该层的 w
否则,应该去除上层的额外宽度 w,同时保留其去除额外宽度之后的高度 h,加入当前层,再送入栈中。

class Solution {
    public int numSubmat(int[][] mat) {
        int m = mat.length, n = mat[0].length, ans = 0;
        int[][] left = new int[m + 1][n + 1];
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (mat[i - 1][j - 1] == 1) {
                    left[i][j] = left[i][j - 1] + 1;
                }
            }
        }
        // 枚举每一列
        for (int j = 0; j < n; ++j) {
            Deque<int[]> stk = new ArrayDeque<>();  // 记录元素是[left[][], height]
            // 从上到下枚举每一行
            for (int i = 0, sum = 0; i < m; ++i) {
                int height = 1;                     // 初始高度为1
                // 维护宽度单调递增的单调队列
                while (!stk.isEmpty() && stk.peek()[0] > left[i + 1][j + 1]) {
                    // 去除无效的额外宽度
                    sum -= stk.peek()[1] * (stk.peek()[0] - left[i + 1][j + 1]);
                    height += stk.pop()[1];         // 继承移除的高度
                }
                sum += left[i + 1][j + 1];          // 矩形数量加上当前层的宽度
                ans += sum;
                stk.push(new int[]{left[i + 1][j + 1], height});
            }
        }
        return ans;
    }
}

相关链接

【算法】单调栈题单(矩阵系列、字典序最小、贡献法)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wei *

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值