【刷题 issue9】程序员代码面试指南 —— IT 名企算法与数据结构题目最优解

第一章 栈和队列

1.9 求最大子矩阵的大小

【题目】

给定一个整形矩阵 matrix,其中的值只有 0 和 1 两种,求其中全是 1 的矩形区域中,最大的矩形区域内 1 的数量。

【难度】

校 ★★★☆

【题解】

如果矩阵的大小为 O(N×M),本题可以做到时间复杂度为 O(N×M)。解法的具体过程如下;

  1. 矩阵的行数为 N,以每一行做切割,统计以当前行作为底,每个位置往上的 1 的数量。使用高度数组 height 表示。
  2. 对于每一次切割,都利用更新后的 height 数组来求出每一行为底时最大的矩形。直到最后最大的矩形即题中所求。
  3. 如果 height 数组的长度为 M,那么求解此过程可以做到时间复杂度为 O(M)。
  4. 对于 height 数组,可以看做一个直方图。那么实质上是在一个大的直方图中求最大矩形的面积。如果求出以每一根柱子拓展出去的最大矩形,找出其中最大的一个即可。
  5. 考虑每一根柱子最大能扩多大,其实就是找到柱子左边第一个比它小的柱子的位置,以及右边第一个比它小的柱子的位置。这一过程用栈很快实现:
  6. 生成一个栈,记为 stack,从左到右遍历 height 数组,每遍历到一个位置,都会把位置压入到 stack 中;
  7. 遍历到 height 的 0 位置时,此时 stack 为空,直接将位置 0 压入栈中;
  8. 遍历到 height 的 i 位置时,只有当前 i 位置的值 height[i] 大于当前栈顶位置所代表的值 height[stack.peek()],则 i 位置才可以压入栈中。所以 stack 中从栈顶到栈底的位置所代表的值是依次递减的,并且无重复值。
  9. 如果当前 i 位置的值 height[i] 小于或等于当前栈顶位置所代表的值 height[stack.peek()],则把栈中存的位置不断弹出,直到栈顶位置所代表的值小于 height[i],再把位置 i 压入栈中,并在此期间做如下处理:
  10. 假设当前弹出的栈顶位置记为 j,弹出栈顶后,新的栈顶记为 k。考虑位置 j 的柱子向右和向左最远能扩到哪里。
  11. 对于位置 j 的柱子,如果 height[j] > height[i],那么 i-1 位置就是向右能扩到的最远位置。因为 j 之所以被弹出,是因为遇到了第一个比位置 j 所代表的值小的位置。如果 height[j] = height[i],那么 i-1 位置不一定是向右能扩到的最远位置。但是可以肯定 i 位置的柱子向左必然可以扩到 j 位置,那么 j 位置的柱子扩出来的最大矩形和 i 位置的柱子扩出来的最大矩形是同一个。所以,此时可以先不计算 j 位置的柱子能扩出来的最大矩形,因为位置 i 是要压入到栈中,等到位置 i 被弹出的时候再进行计算。
  12. 对于位置 j 的柱子,向左最远能扩到 k+1 位置。首先,height[k+1…j-1] 之间不可能有小于或等于 height[k] 的值,否则 k 位置早就被弹出了。又因为再栈中 k 位置和 j 位置原本是相邻的,并且从栈顶到栈底的位置所代表的值是依次递减并且无重复值,所以在 height[k+1…j-1] 之间不可能有大于或等于 height[k],同时又小于或等于 height[j] 的值,否则 k 和 j 就不可能相邻。所以,height[k+1…j-1] 之间的值既大于 height[k],又大于 height [j]。于是,j 位置的柱子向左最远可以扩到 k+1 位置。
  13. 综上所述,j 位置的柱子能扩出来的最大矩形为 (i-k-1)*height[j]。
  14. 在遍历结束后,stack 中可能仍有位置没有经历扩的过程。此时因为 height 数组不能再向右扩了,所以认为 i = height.length 且越界之后的值非常小,然后开始弹出留在栈中的位置。等到栈为空,这个过程就结束了。

通过上述步骤,任何一个位置仅仅进出栈一次,所以时间复杂度为 O(M)。每做一次切割处理的时间复杂度为 O(M),一共做 N 次,则总的时间复杂度为 O(N×M)。

【实现】

  • Matrix.java
import java.util.Stack;

public class Matrix {

    private int[][] matrix;
    private Integer maxSize;

    public Matrix(int[][] arr) {
        this.matrix = arr;
    }

    public void setMatrix(int[][] arr) {
        this.matrix = arr;
        compute();
    }

    public int getMaxSubMatrixSize() {
        if (this.maxSize == null) {
            compute();
        }
        return this.maxSize;
    }

    private void compute() {
        if (this.matrix == null || this.matrix.length == 0 || this.matrix[0].length == 0) {
            this.maxSize = 0;
            return;
        }
        int[] height = new int[this.matrix[0].length];
        for (int i = 0; i < this.matrix.length; ++i) {
            for (int j = 0; j < this.matrix[0].length; ++j) {
                height[j] = this.matrix[i][j] == 0 ? 0 : height[j] + 1;
            }
            maxSize(height);
        }
    }

    private void maxSize(int[] height) {
        if (height == null || height.length == 0) {
            return;
        }
        this.maxSize = 0;
        Stack<Integer> stack = new Stack<>();
        /**
         * 遍历 height 数组
         */
        for (int i = 0; i < height.length; ++i) {
            while (!stack.empty() && height[i] <= height[stack.peek()]) {
                int j = stack.pop();
                int k = stack.empty() ? -1 : stack.peek();
                int size = (i - k - 1) * height[j];
                if (size > this.maxSize) {
                    this.maxSize = size;
                }
            }
            stack.push(i);  // 索引
        }
        /**
         * 处理 height 数组遍历完后 stack 未空的情况
         */
        while (!stack.empty()) {
            int j = stack.pop();
            int k = stack.empty() ? -1 : stack.peek();
            int size = (height.length - k - 1) * height[j];
            if (size > this.maxSize) {
                this.maxSize = size;
            }
        }
    }

}
  • MatrixTest.java
public class MatrixTest {

    public static void main(String[] args) {
        int[][] arr = {
                {1, 0, 1, 1},
                {0, 1, 0, 1},
                {1, 1, 1, 0},
        };
        Matrix map = new Matrix(arr);
        int size = map.getMaxSubMatrixSize();
        System.out.println(size);
    }

}
发布了147 篇原创文章 · 获赞 72 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Pranuts_/article/details/100167531