拼图:找到最大的矩形(最大矩形问题)

时间:2008-08-10 17:16:55

标签: algorithm language-agnostic math geometry

找到最适合空白区域的矩形的最有效算法是什么?

假设屏幕看起来像这样('#'代表填充区域):

....................
..............######
##..................
.................###
.................###
#####...............
#####...............
#####...............

一个可能的解决方案是:

....................
..............######
##...++++++++++++...
.....++++++++++++###
.....++++++++++++###
#####++++++++++++...
#####++++++++++++...
#####++++++++++++...

通常情况下,我很乐意找出解决方案。虽然这次我想避免浪费时间自己摸索,因为这对我正在研究的项目有实际用途。有一个众所周知的解决方案吗?

Shog9 写道:

  

您的输入是否为数组(如其他响应所暗示的),或者是任意大小的定位矩形形式的遮挡列表(在处理窗口位置时窗口系统中可能就是这种情况)?

是的,我有一个跟踪屏幕上放置的一组窗口的结构。我还有一个网格,可以跟踪每个边缘之间的所有区域,无论它们是空的还是填充的,以及它们左边或顶边的像素位置。我认为有一些修改后的形式可以利用这个属性。你知道吗?

8 个答案:

答案 0 :(得分:21)

我是Dobb博士文章的作者,偶尔​​也会被问及实施情况。这是C中的一个简单的:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
  int one;
  int two;
} Pair;

Pair best_ll = { 0, 0 };
Pair best_ur = { -1, -1 };
int best_area = 0;

int *c; /* Cache */
Pair *s; /* Stack */
int top = 0; /* Top of stack */

void push(int a, int b) {
  s[top].one = a;
  s[top].two = b;
  ++top;
}

void pop(int *a, int *b) {
  --top;
  *a = s[top].one;
  *b = s[top].two;
}


int M, N; /* Dimension of input; M is length of a row. */

void update_cache() {
  int m;
  char b;
  for (m = 0; m!=M; ++m) {
    scanf(" %c", &b);
    fprintf(stderr, " %c", b);
    if (b=='0') {
      c[m] = 0;
    } else { ++c[m]; }
  }
  fprintf(stderr, "\n");
}


int main() {
  int m, n;
  scanf("%d %d", &M, &N);
  fprintf(stderr, "Reading %dx%d array (1 row == %d elements)\n", M, N, M);
  c = (int*)malloc((M+1)*sizeof(int));
  s = (Pair*)malloc((M+1)*sizeof(Pair));
  for (m = 0; m!=M+1; ++m) { c[m] = s[m].one = s[m].two = 0; }
  /* Main algorithm: */
  for (n = 0; n!=N; ++n) {
    int open_width = 0;
    update_cache();
    for (m = 0; m!=M+1; ++m) {
      if (c[m]>open_width) { /* Open new rectangle? */
        push(m, open_width);
        open_width = c[m];
      } else /* "else" optional here */
      if (c[m]<open_width) { /* Close rectangle(s)? */
        int m0, w0, area;
        do {
          pop(&m0, &w0);
          area = open_width*(m-m0);
          if (area>best_area) {
            best_area = area;
            best_ll.one = m0; best_ll.two = n;
            best_ur.one = m-1; best_ur.two = n-open_width+1;
          }
          open_width = w0;
        } while (c[m]<open_width);
        open_width = c[m];
        if (open_width!=0) {
          push(m0, w0);
        }
      }
    }
  }
  fprintf(stderr, "The maximal rectangle has area %d.\n", best_area);
  fprintf(stderr, "Location: [col=%d, row=%d] to [col=%d, row=%d]\n",
                  best_ll.one+1, best_ll.two+1, best_ur.one+1, best_ur.two+1);
  return 0;
}

它从控制台获取输入。你可以,例如将此文件传递给它:

16 12
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 0
0 0 0 1 1 0 0 1 0 0 0 1 1 0 1 0
0 0 0 1 1 0 1 1 1 0 1 1 1 0 1 0
0 0 0 0 1 1 * * * * * * 0 0 1 0
0 0 0 0 0 0 * * * * * * 0 0 1 0
0 0 0 0 0 0 1 1 0 1 1 1 1 1 1 0
0 0 1 0 0 0 0 1 0 0 1 1 1 0 1 0 
0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0

打印输入后,输出:

The maximal rectangle has area 12.
Location: [col=7, row=6] to [col=12, row=5]

上面的实现当然没什么特别的,但是它与Dobb博士的文章中的解释非常接近,应该很容易转化为所需要的东西。

答案 1 :(得分:20)

@lassevk

我在DDJ中找到了引用的文章:The Maximal Rectangle Problem

答案 2 :(得分:4)

这是一个包含一些代码和一些分析的页面。

您的特定问题在页面上稍微开始,在页面中搜索文本最大矩形问题

http://www.seas.gwu.edu/~simhaweb/cs151/lectures/module6/module6.html

答案 3 :(得分:2)

@lassevk

    // 4. Outer double-for-loop to consider all possible positions 
    //    for topleft corner. 
    for (int i=0; i < M; i++) {
      for (int j=0; j < N; j++) {

        // 2.1 With (i,j) as topleft, consider all possible bottom-right corners. 

        for (int a=i; a < M; a++) {
          for (int b=j; b < N; b++) {

HAHA ...... O(m2 n2)..这可能是我想出的。

我看到他们继续开发优化项目......看起来不错,我会读一读。

答案 4 :(得分:2)

我在Java中实现了Dobbs的解决方案。

不保证任何事情。

package com.test;

import java.util.Stack;

public class Test {

    public static void main(String[] args) {
        boolean[][] test2 = new boolean[][] { new boolean[] { false, true, true, false },
                new boolean[] { false, true, true, false }, new boolean[] { false, true, true, false },
                new boolean[] { false, true, false, false } };
        solution(test2);
    }

    private static class Point {
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int x;
        public int y;
    }

    public static int[] updateCache(int[] cache, boolean[] matrixRow, int MaxX) {
        for (int m = 0; m < MaxX; m++) {
            if (!matrixRow[m]) {
                cache[m] = 0;
            } else {
                cache[m]++;
            }
        }
        return cache;
    }

    public static void solution(boolean[][] matrix) {
        Point best_ll = new Point(0, 0);
        Point best_ur = new Point(-1, -1);
        int best_area = 0;

        final int MaxX = matrix[0].length;
        final int MaxY = matrix.length;

        Stack<Point> stack = new Stack<Point>();
        int[] cache = new int[MaxX + 1];

        for (int m = 0; m != MaxX + 1; m++) {
            cache[m] = 0;
        }

        for (int n = 0; n != MaxY; n++) {
            int openWidth = 0;
            cache = updateCache(cache, matrix[n], MaxX);
            for (int m = 0; m != MaxX + 1; m++) {
                if (cache[m] > openWidth) {
                    stack.push(new Point(m, openWidth));
                    openWidth = cache[m];
                } else if (cache[m] < openWidth) {
                    int area;
                    Point p;
                    do {
                        p = stack.pop();
                        area = openWidth * (m - p.x);
                        if (area > best_area) {
                            best_area = area;
                            best_ll.x = p.x;
                            best_ll.y = n;
                            best_ur.x = m - 1;
                            best_ur.y = n - openWidth + 1;
                        }
                        openWidth = p.y;
                    } while (cache[m] < openWidth);
                    openWidth = cache[m];
                    if (openWidth != 0) {
                        stack.push(p);
                    }
                }
            }
        }

        System.out.printf("The maximal rectangle has area %d.\n", best_area);
        System.out.printf("Location: [col=%d, row=%d] to [col=%d, row=%d]\n", best_ll.x + 1, best_ll.y + 1,
                best_ur.x + 1, best_ur.y + 1);
    }

}

答案 5 :(得分:2)

经过艰苦的努力,我写了这个算法......只看代码......

你理解。这段代码说!!

[33, 53, 12, 64, 41, 45, 21, 97, 35, 47, 39, 93, 40, 46, 42, 95, 51, 68, 9,
 84, 34, 64, 6, 26, 3, 43, 30, 60, 3, 68, 9, 97, 19, 27, 4, 96, 37, 78, 43,
 64, 4, 65, 30, 84, 18, 50, 1, 40, 32, 76, 57, 63, 53, 57, 42, 80, 9, 41, 30,
 79, 18, 97, 23, 52, 38, 74, 15]

[33, 53, 12, 64, 41, 45, 21, 97, 35, 92, 0, 93, 40, 69, 6, 95, 51, 72, 9, 84, 34, 64, 2, 98, 3, 43, 30, 60, 3, 82, 9, 97, 19, 99, 4, 96, 9, 78, 43, 64, 4, 65, 30, 90, 18, 60, 1, 40, 32, 100, 29, 63, 46, 98, 42, 82, 9, 84, 30, 79, 18, 97, 23, 52, 38, 74]

答案 6 :(得分:1)

I am the author of the Maximal Rectangle Solution on LeetCode, which is what this answer is based on.

Since the stack-based solution has already been discussed in the other answers, I would like to present an optimal O(NM) dynamic programming solution which originates from user morrischen2008.

Intuition

Imagine an algorithm where for each point we computed a rectangle by doing the following:

  • Finding the maximum height of the rectangle by iterating upwards until a filled area is reached

  • Finding the maximum width of the rectangle by iterating outwards left and right until a height that doesn't accommodate the maximum height of the rectangle

For example finding the rectangle defined by the yellow point: enter image description here

We know that the maximal rectangle must be one of the rectangles constructed in this manner (the max rectangle must have a point on its base where the next filled square is height above that point).

For each point we define some variables:

h - the height of the rectangle defined by that point

l - the left bound of the rectangle defined by that point

r - the right bound of the rectangle defined by that point

These three variables uniquely define the rectangle at that point. We can compute the area of this rectangle with h * (r - l). The global maximum of all these areas is our result.

Using dynamic programming, we can use the h, l, and r of each point in the previous row to compute the h, l, and r for every point in the next row in linear time.

Algorithm

Given row matrix[i], we keep track of the h, l, and r of each point in the row by defining three arrays - height, left, and right.

height[j] will correspond to the height of matrix[i][j], and so on and so forth with the other arrays.

The question now becomes how to update each array.

height

h is defined as the number of continuous unfilled spaces in a line from our point. We increment if there is a new space, and set it to zero if the space is filled (we are using '1' to indicate an empty space and '0' as a filled one).

new_height[j] = old_height[j] + 1 if row[j] == '1' else 0

left:

Consider what causes changes to the left bound of our rectangle. Since all instances of filled spaces occurring in the row above the current one have already been factored into the current version of left, the only thing that affects our left is if we encounter a filled space in our current row.

As a result we can define:

new_left[j] = max(old_left[j], cur_left)

cur_left is one greater than rightmost filled space we have encountered. When we "expand" the rectangle to the left, we know it can't expand past that point, otherwise it'll run into the filled space.

right:

Here we can reuse our reasoning in left and define:

new_right[j] = min(old_right[j], cur_right)

cur_right is the leftmost occurrence of a filled space we have encountered.

Implementation

def maximalRectangle(matrix):
    if not matrix: return 0

    m = len(matrix)
    n = len(matrix[0])

    left = [0] * n # initialize left as the leftmost boundary possible
    right = [n] * n # initialize right as the rightmost boundary possible
    height = [0] * n

    maxarea = 0

    for i in range(m):

        cur_left, cur_right = 0, n
        # update height
        for j in range(n):
            if matrix[i][j] == '1': height[j] += 1
            else: height[j] = 0
        # update left
        for j in range(n):
            if matrix[i][j] == '1': left[j] = max(left[j], cur_left)
            else:
                left[j] = 0
                cur_left = j + 1
        # update right
        for j in range(n-1, -1, -1):
            if matrix[i][j] == '1': right[j] = min(right[j], cur_right)
            else:
                right[j] = n
                cur_right = j
        # update the area
        for j in range(n):
            maxarea = max(maxarea, height[j] * (right[j] - left[j]))

    return maxarea

答案 7 :(得分:0)

使用纯Javascript实现基于堆栈的算法:

function maxRectangle(mask) {
  var best = {area: 0}
  const width = mask[0].length
  const depth = Array(width).fill(0)
  for (var y = 0; y < mask.length; y++) {
    const ranges = Array()
    for (var x = 0; x < width; x++) {
      const d = depth[x] = mask[y][x] ? depth[x] + 1 : 0
      if (!ranges.length || ranges[ranges.length - 1].height < d) {
        ranges.push({left: x, height: d})
      } else {
        for (var j = ranges.length - 1; j >= 0 && ranges[j].height >= d; j--) {
          const {left, height} = ranges[j]
          const area = (x - left) * height
          if (area > best.area) {
            best = {area, left, top: y + 1 - height, right: x, bottom: y + 1}
          }
        }
        ranges.splice(j+2)
        ranges[j+1].height = d
      }
    }
  }
  return best;
}

var example = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0],
[0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0],
[0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]

console.log(maxRectangle(example))