[ACCEPTED]-Puzzle: Find largest rectangle (maximal rectangle problem)-geometry
I'm the author of that Dr. Dobb's article 9 and get occasionally asked about an implementation. Here 8 is a simple one in 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;
}
It takes its input 7 from the console. You could e.g. pipe this 6 file to it:
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
And after printing its input, it 5 will output:
The maximal rectangle has area 12.
Location: [col=7, row=6] to [col=12, row=5]
The implementation above is 4 nothing fancy of course, but it's very close 3 to the explanation in the Dr. Dobb's article 2 and should be easy to translate to whatever 1 is needed.
I am the author of the Maximal Rectangle Solution on LeetCode, which 56 is what this answer is based on.
Since the 55 stack-based solution has already been discussed 54 in the other answers, I would like to present 53 an optimal O(NM)
dynamic programming solution 52 which originates from user morrischen2008.
Intuition
Imagine an algorithm 51 where for each point we computed a rectangle 50 by doing the following:
Finding the maximum 49 height of the rectangle by iterating upwards 48 until a filled area is reached
Finding the 47 maximum width of the rectangle by iterating 46 outwards left and right until a height that 45 doesn't accommodate the maximum height of 44 the rectangle
For example finding the rectangle 43 defined by the yellow point:
We know that 42 the maximal rectangle must be one of the 41 rectangles constructed in this manner (the 40 max rectangle must have a point on its base 39 where the next filled square is height above that 38 point).
For each point we define some variables:
h
- the 37 height of the rectangle defined by that 36 point
l
- the left bound of the rectangle 35 defined by that point
r
- the right bound 34 of the rectangle defined by that point
These 33 three variables uniquely define the rectangle 32 at that point. We can compute the area of 31 this rectangle with h * (r - l)
. The global maximum 30 of all these areas is our result.
Using 29 dynamic programming, we can use the h
, l
, and 28 r
of each point in the previous row to compute 27 the h
, l
, and r
for every point in the next 26 row in linear time.
Algorithm
Given row matrix[i]
, we keep track 25 of the h
, l
, and r
of each point in the row 24 by defining three arrays - height
, left
, and right
.
height[j]
will 23 correspond to the height of matrix[i][j]
, and so on 22 and so forth with the other arrays.
The question 21 now becomes how to update each array.
height
h
is 20 defined as the number of continuous unfilled 19 spaces in a line from our point. We increment 18 if there is a new space, and set it to zero 17 if the space is filled (we are using '1' to 16 indicate an empty space and '0' as a filled 15 one).
new_height[j] = old_height[j] + 1 if row[j] == '1' else 0
left
:
Consider what causes changes to the 14 left bound of our rectangle. Since all instances 13 of filled spaces occurring in the row above 12 the current one have already been factored 11 into the current version of left
, the only thing 10 that affects our left
is if we encounter a filled 9 space in our current row.
As a result we 8 can define:
new_left[j] = max(old_left[j], cur_left)
cur_left
is one greater than rightmost 7 filled space we have encountered. When we 6 "expand" the rectangle to the 5 left, we know it can't expand past that 4 point, otherwise it'll run into the filled 3 space.
right
:
Here we can reuse our reasoning in 2 left
and define:
new_right[j] = min(old_right[j], cur_right)
cur_right
is the leftmost occurrence 1 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
I implemented the solution of Dobbs in Java.
No 1 warranty for anything.
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);
}
}
@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).. That's probably 3 what I would have come up with.
I see they 2 go on to develop optmizations... looks good, I'll 1 have a read.
After struggling so much I've wrote this 2 algorithm...Just see the code...
You understand 1 that.This code speaks !!
import java.util.Scanner;
import java.util.Stack;
/**
* Created by BK on 05-08-2017.
*/
public class LargestRectangleUnderHistogram {
public static void main(String... args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] input = new int[n];
for (int j = 0; j < n; j++) {
input[j] = scanner.nextInt();
}
/*
* This is the procedure used for solving :
*
* Travel from first element to last element of the array
*
* If stack is empty add current element to stack
*
* If stack is not empty check for the top element of stack if
* it is smaller than the current element push into stack
*
* If it is larger than the current element pop the stack until we get an
* element smaller than the current element or until stack becomes empty
*
* After popping each element check if the stack is empty or not..
*
* If stack is empty it means that this is the smallest element encountered till now
*
* So we can multiply i with this element to get a big rectangle which is contributed by
*
* this
*
* If stack is not empty then check for individual areas(Not just one bar individual area means largest rectangle by this) (i-top)*input[top]
*
*
* */
/*
* Initializing the maxarea as we check each area with maxarea
*/
int maxarea = -1;
int i = 0;
Stack<Integer> stack = new Stack<>();
for (i = 0; i < input.length; i++) {
/*
* Pushing the element if stack is empty
* */
if (stack.isEmpty()) {
stack.push(i);
} else {
/*
* If stack top element is less than current element push
* */
if (input[stack.peek()] < input[i]) {
stack.push(i);
} else {
/*
* Else pop until we encounter an element smaller than this in stack or stack becomes empty
*
* */
while (!stack.isEmpty() && input[stack.peek()] > input[i]) {
int top = stack.pop();
/*
* If stack is empty means this is the smallest element encountered so far
*
* So we can multiply this with i
* */
if (stack.isEmpty()) {
maxarea = maxarea < (input[top] * i) ? (input[top] * i) : maxarea;
}
/*
* If stack is not empty we find areas of each individual rectangle
* Remember we are in while loop
*/
else {
maxarea = maxarea < (input[top] * (i - top)) ? (input[top] * (i - top)) : maxarea;
}
}
/*
* Finally pushing the current element to stack
* */
stack.push(i);
}
}
}
/*
* This is for checking if stack is not empty after looping the last element of input
*
* This happens if input is like this 4 5 6 1 2 3 4 5
*
* Here 2 3 4 5 remains in stack as they are always increasing and we never got
*
* a chance to pop them from stack by above process
*
* */
while (!stack.isEmpty()) {
int top = stack.pop();
maxarea = maxarea < (i - top) * input[top] ? (i - top) * input[top] : maxarea;
}
System.out.println(maxarea);
}
}
Implementation of the stack-based algorithm 1 in plain Javascript (with linear time complexity):
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))
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.