题目描述(困难难度)

85. Maximal Rectangle - 图1

给一个只有 0 和 1 的矩阵,输出一个最大的矩形的面积,这个矩形里边只含有 1。

解法一 暴力破解

参考这里-solution-for-your-reference>),遍历每个点,求以这个点为矩阵右下角的所有矩阵面积。如下图的两个例子,橙色是当前遍历的点,然后虚线框圈出的矩阵是其中一个矩阵。

85. Maximal Rectangle - 图2

怎么找出这样的矩阵呢?如下图,如果我们知道了以这个点结尾的连续 1 的个数的话,问题就变得简单了。

85. Maximal Rectangle - 图3

  1. 首先求出高度是 1 的矩形面积,也就是它自身的数,也就是上图以橙色的 4 结尾的 「1234」的那个矩形,面积就是 4。

  2. 然后向上扩展一行,高度增加一,选出当前列最小的数字,作为矩阵的宽,如上图,当前列中有 24 ,那么,就将 2 作为矩形的宽,求出面积,对应上图的矩形圈出的部分。

  3. 然后继续向上扩展,重复步骤 2。

按照上边的方法,遍历所有的点,以当前点为矩阵的右下角,求出所有的矩阵就可以了。下图是某一个点的过程。

以橙色的点为右下角,高度为 1。

85. Maximal Rectangle - 图4

高度为 2。

85. Maximal Rectangle - 图5

高度为 3。

85. Maximal Rectangle - 图6

代码的话,把求每个点累计的连续 1 的个数用 width 保存,同时把求最大矩形的面积和求 width融合到同一个循环中。

  1. public int maximalRectangle(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. //保存以当前数字结尾的连续 1 的个数
  6. int[][] width = new int[matrix.length][matrix[0].length];
  7. int maxArea = 0;
  8. //遍历每一行
  9. for (int row = 0; row < matrix.length; row++) {
  10. for (int col = 0; col < matrix[0].length; col++) {
  11. //更新 width
  12. if (matrix[row][col] == '1') {
  13. if (col == 0) {
  14. width[row][col] = 1;
  15. } else {
  16. width[row][col] = width[row][col - 1] + 1;
  17. }
  18. } else {
  19. width[row][col] = 0;
  20. }
  21. //记录所有行中最小的数
  22. int minWidth = width[row][col];
  23. //向上扩展行
  24. for (int up_row = row; up_row >= 0; up_row--) {
  25. int height = row - up_row + 1;
  26. //找最小的数作为矩阵的宽
  27. minWidth = Math.min(minWidth, width[up_row][col]);
  28. //更新面积
  29. maxArea = Math.max(maxArea, height * minWidth);
  30. }
  31. }
  32. }
  33. return maxArea;
  34. }

时间复杂度:O(m²n)。

空间复杂度:O(mn)。

解法二

参考这里-solution-based-on-Largest-Rectangle-in-Histogram>),接下来的解法,会让这道题变得异常简单。还记得 84 题吗?求一个直方图矩形的最大面积。

85. Maximal Rectangle - 图7

大家可以先做 84 题,然后回来考虑这道题。

再想一下这个题,看下边的橙色的部分,这完全就是上一道题呀!

85. Maximal Rectangle - 图8

算法有了,就是求出每一层的 heights[] 然后传给上一题的函数就可以了。

利用上一题的栈解法。

  1. public int maximalRectangle(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. int[] heights = new int[matrix[0].length];
  6. int maxArea = 0;
  7. for (int row = 0; row < matrix.length; row++) {
  8. //遍历每一列,更新高度
  9. for (int col = 0; col < matrix[0].length; col++) {
  10. if (matrix[row][col] == '1') {
  11. heights[col] += 1;
  12. } else {
  13. heights[col] = 0;
  14. }
  15. }
  16. //调用上一题的解法,更新函数
  17. maxArea = Math.max(maxArea, largestRectangleArea(heights));
  18. }
  19. return maxArea;
  20. }
  21. public int largestRectangleArea(int[] heights) {
  22. int maxArea = 0;
  23. Stack<Integer> stack = new Stack<>();
  24. int p = 0;
  25. while (p < heights.length) {
  26. //栈空入栈
  27. if (stack.isEmpty()) {
  28. stack.push(p);
  29. p++;
  30. } else {
  31. int top = stack.peek();
  32. //当前高度大于栈顶,入栈
  33. if (heights[p] >= heights[top]) {
  34. stack.push(p);
  35. p++;
  36. } else {
  37. //保存栈顶高度
  38. int height = heights[stack.pop()];
  39. //左边第一个小于当前柱子的下标
  40. int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
  41. //右边第一个小于当前柱子的下标
  42. int RightLessMin = p;
  43. //计算面积
  44. int area = (RightLessMin - leftLessMin - 1) * height;
  45. maxArea = Math.max(area, maxArea);
  46. }
  47. }
  48. }
  49. while (!stack.isEmpty()) {
  50. //保存栈顶高度
  51. int height = heights[stack.pop()];
  52. //左边第一个小于当前柱子的下标
  53. int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
  54. //右边没有小于当前高度的柱子,所以赋值为数组的长度便于计算
  55. int RightLessMin = heights.length;
  56. int area = (RightLessMin - leftLessMin - 1) * height;
  57. maxArea = Math.max(area, maxArea);
  58. }
  59. return maxArea;
  60. }

时间复杂度:O(mn)。

空间复杂度:O(n)。

利用上一题的解法四。

  1. public int maximalRectangle(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. int[] heights = new int[matrix[0].length];
  6. int maxArea = 0;
  7. for (int row = 0; row < matrix.length; row++) {
  8. //遍历每一列,更新高度
  9. for (int col = 0; col < matrix[0].length; col++) {
  10. if (matrix[row][col] == '1') {
  11. heights[col] += 1;
  12. } else {
  13. heights[col] = 0;
  14. }
  15. }
  16. //调用上一题的解法,更新函数
  17. maxArea = Math.max(maxArea, largestRectangleArea(heights));
  18. }
  19. return maxArea;
  20. }
  21. public int largestRectangleArea(int[] heights) {
  22. if (heights.length == 0) {
  23. return 0;
  24. }
  25. int[] leftLessMin = new int[heights.length];
  26. leftLessMin[0] = -1;
  27. for (int i = 1; i < heights.length; i++) {
  28. int l = i - 1;
  29. while (l >= 0 && heights[l] >= heights[i]) {
  30. l = leftLessMin[l];
  31. }
  32. leftLessMin[i] = l;
  33. }
  34. int[] rightLessMin = new int[heights.length];
  35. rightLessMin[heights.length - 1] = heights.length;
  36. for (int i = heights.length - 2; i >= 0; i--) {
  37. int r = i + 1;
  38. while (r <= heights.length - 1 && heights[r] >= heights[i]) {
  39. r = rightLessMin[r];
  40. }
  41. rightLessMin[i] = r;
  42. }
  43. int maxArea = 0;
  44. for (int i = 0; i < heights.length; i++) {
  45. int area = (rightLessMin[i] - leftLessMin[i] - 1) * heights[i];
  46. maxArea = Math.max(area, maxArea);
  47. }
  48. return maxArea;
  49. }

时间复杂度:O(mn)。

空间复杂度:O(n)。

解法三

解法二中套用的栈的解法,我们其实可以不用调用函数,而是把栈糅合到原来求 heights 中。因为栈的话并不是一次性需要所有的高度,所以可以求出一个高度,然后就操作栈。

  1. public int maximalRectangle(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. int[] heights = new int[matrix[0].length + 1]; //小技巧后边讲
  6. int maxArea = 0;
  7. for (int row = 0; row < matrix.length; row++) {
  8. Stack<Integer> stack = new Stack<Integer>();
  9. heights[matrix[0].length] = 0;
  10. //每求一个高度就进行栈的操作
  11. for (int col = 0; col <= matrix[0].length; col++) {
  12. if (col < matrix[0].length) { //多申请了 1 个元素,所以要判断
  13. if (matrix[row][col] == '1') {
  14. heights[col] += 1;
  15. } else {
  16. heights[col] = 0;
  17. }
  18. }
  19. if (stack.isEmpty() || heights[col] >= heights[stack.peek()]) {
  20. stack.push(col);
  21. } else {
  22. //每次要判断新的栈顶是否高于当前元素
  23. while (!stack.isEmpty() && heights[col] < heights[stack.peek()]) {
  24. int height = heights[stack.pop()];
  25. int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
  26. int RightLessMin = col;
  27. int area = (RightLessMin - leftLessMin - 1) * height;
  28. maxArea = Math.max(area, maxArea);
  29. }
  30. stack.push(col);
  31. }
  32. }
  33. }
  34. return maxArea;
  35. }

时间复杂度:O(mn)。

空间复杂度:O(n)。

里边有一个小技巧,84 题 的栈解法中,我们用了两个 while 循环,第二个 while 循环用来解决遍历完元素栈不空的情况。其实,我们注意到两个 while 循环的逻辑完全一样的。所以我们可以通过一些操作,使得遍历结束后,依旧进第一个 while 循环,从而剩下了第 2 个 while 循环,代码看起来会更简洁。

那就是 heights 多申请一个元素,赋值为 0。这样最后一次遍历的时候,栈顶肯定会大于当前元素,所以就进入了第一个 while 循环。

解法四 动态规划

参考这里,这是 leetcode Solution 中投票最高的,但比较难理解,但如果结合 84 题去想的话就很容易了。

解法二中,用了 84 题的两个解法,解法三中我们把栈糅合进了原算法,那么另一种可以一样的思路吗?不行!因为栈不要求所有的高度,可以边更新,边处理。而另一种,是利用两个数组, leftLessMin [ ] 和 rightLessMin [ ]。而这两个数组的更新,是需要所有高度的。

解法二中,我们更新一次 heights,就利用之前的算法,求一遍 leftLessMin [ ] 和 rightLessMin [ ],然后更新面积。而其实,我们求 leftLessMin [ ] 和 rightLessMin [ ] 可以利用之前的 leftLessMin [ ] 和 rightLessMin [ ] 来更新本次的。

我们回想一下 leftLessMin [ ] 和 rightLessMin [ ] 的含义, leftLessMin [ i ] 代表左边第一个比当前柱子矮的下标,如下图橙色柱子时当前遍历的柱子。rightLessMin [ ] 时右边第一个。

85. Maximal Rectangle - 图9

left 和 right 是对称关系,下边只考虑 left 的求法。

如下图,如果当前新增的层全部是 1,当然这时最完美的情况,那么 leftLessMin [ ] 根本不需要改变。

85. Maximal Rectangle - 图10

然而事实是残酷的,一定会有 0 的出现。

85. Maximal Rectangle - 图11

我们考虑最后一个柱子的更新。上一层的 leftLessMin = 1,也就是蓝色 0 的位置是第一个比它低的柱子。但是在当前层,由于中间出现了 0。所以不再是之前的 leftLessMin ,而是和上次出现 0 的位置进行比较(因为 0 一定比当前柱子小),谁的下标大,更接近当前柱子,就选择谁。上图中出现 0 的位置是 2,之前的 leftLessMin 是 1,选一个较大的,那就是 2 了。

  1. public int maximalRectangle4(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. int maxArea = 0;
  6. int cols = matrix[0].length;
  7. int[] leftLessMin = new int[cols];
  8. int[] rightLessMin = new int[cols];
  9. Arrays.fill(leftLessMin, -1); //初始化为 -1,也就是最左边
  10. Arrays.fill(rightLessMin, cols); //初始化为 cols,也就是最右边
  11. int[] heights = new int[cols];
  12. for (int row = 0; row < matrix.length; row++) {
  13. //更新所有高度
  14. for (int col = 0; col < cols; col++) {
  15. if (matrix[row][col] == '1') {
  16. heights[col] += 1;
  17. } else {
  18. heights[col] = 0;
  19. }
  20. }
  21. //更新所有leftLessMin
  22. int boundary = -1; //记录上次出现 0 的位置
  23. for (int col = 0; col < cols; col++) {
  24. if (matrix[row][col] == '1') {
  25. //和上次出现 0 的位置比较
  26. leftLessMin[col] = Math.max(leftLessMin[col], boundary);
  27. } else {
  28. //当前是 0 代表当前高度是 0,所以初始化为 -1,防止对下次循环的影响
  29. leftLessMin[col] = -1;
  30. //更新 0 的位置
  31. boundary = col;
  32. }
  33. }
  34. //右边同理
  35. boundary = cols;
  36. for (int col = cols - 1; col >= 0; col--) {
  37. if (matrix[row][col] == '1') {
  38. rightLessMin[col] = Math.min(rightLessMin[col], boundary);
  39. } else {
  40. rightLessMin[col] = cols;
  41. boundary = col;
  42. }
  43. }
  44. //更新所有面积
  45. for (int col = cols - 1; col >= 0; col--) {
  46. int area = (rightLessMin[col] - leftLessMin[col] - 1) * heights[col];
  47. maxArea = Math.max(area, maxArea);
  48. }
  49. }
  50. return maxArea;
  51. }

时间复杂度:O(mn)。

空间复杂度:O(n)。

有时候,如果可以把题抽象到已解决的问题当中去,可以大大的简化问题,很酷!

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情