80 votes

Trouver le plus grand rectangle contenant uniquement des zéros dans une matrice binaire N×N

Étant donné une matrice binaire NxN (contenant uniquement des 0 ou des 1), comment faire pour trouver le plus grand rectangle contenant tous les 0 ?

Exemple :

      I
    0 0 0 0 1 0
    0 0 1 0 0 1
II->0 0 0 0 0 0
    1 0 0 0 0 0
    0 0 0 0 0 1 <--IV
    0 0 1 0 0 0
            IV 

Pour l'exemple ci-dessus, il s'agit d'une matrice binaire 6×6. La valeur de retour dans ce cas sera Cellule 1 :(2, 1) et Cellule 2 :(4, 4). La sous-matrice résultante peut être carrée ou rectangulaire. La valeur de retour peut également être la taille de la plus grande sous-matrice de tous les 0, dans cet exemple 3 × 4.

1 votes

Veuillez envisager de changer la réponse acceptée par la réponse de J.F. Sebastian, qui est maintenant correcte et dont la complexité est optimale.

1 votes

Veuillez vérifier les questions très similaires (je dirais même dupliquées) : stackoverflow.com/questions/7770945/ , stackoverflow.com/a/7353193/684229 . La solution est O(n) .

0 votes

J'essaie de faire la même chose avec un rectangle orienté dans n'importe quelle direction. Voir la question : stackoverflow.com/questions/22604043/

46voto

J.F. Sebastian Points 102961

Voici une solution basée sur le Problème du "plus grand rectangle dans un histogramme". suggéré par @j_random_hacker dans les commentaires :

[L'algorithme fonctionne en itérant à travers les rangées de haut en bas, pour chaque rangée en résolvant ce problème où les les "barres" de l'"histogramme" sont constituées de toutes les traces ascendantes ininterrompues de zéros qui commencent à la ligne actuelle (une colonne a la hauteur 0 si elle a un 1 dans la ligne la ligne actuelle).

La matrice d'entrée mat peut être un itérable arbitraire, par exemple un fichier ou un flux réseau. Une seule ligne doit être disponible à la fois.

#!/usr/bin/env python
from collections import namedtuple
from operator import mul

Info = namedtuple('Info', 'start height')

def max_size(mat, value=0):
    """Find height, width of the largest rectangle containing all `value`'s."""
    it = iter(mat)
    hist = [(el==value) for el in next(it, [])]
    max_size = max_rectangle_size(hist)
    for row in it:
        hist = [(1+h) if el == value else 0 for h, el in zip(hist, row)]
        max_size = max(max_size, max_rectangle_size(hist), key=area)
    return max_size

def max_rectangle_size(histogram):
    """Find height, width of the largest rectangle that fits entirely under
    the histogram.
    """
    stack = []
    top = lambda: stack[-1]
    max_size = (0, 0) # height, width of the largest rectangle
    pos = 0 # current position in the histogram
    for pos, height in enumerate(histogram):
        start = pos # position where rectangle starts
        while True:
            if not stack or height > top().height:
                stack.append(Info(start, height)) # push
            elif stack and height < top().height:
                max_size = max(max_size, (top().height, (pos - top().start)),
                               key=area)
                start, _ = stack.pop()
                continue
            break # height == top().height goes here

    pos += 1
    for start, height in stack:
        max_size = max(max_size, (height, (pos - start)), key=area)    
    return max_size

def area(size):
    return reduce(mul, size)

La solution est O(N) , donde N est le nombre d'éléments d'une matrice. Il nécessite O(ncols) une mémoire supplémentaire, où ncols est le nombre de colonnes d'une matrice.

La dernière version avec les tests se trouve à l'adresse suivante https://gist.github.com/776423

2 votes

Bien essayé, mais c'est raté max_size([[0,0,0,0,1,1,1], [0,0,0,0,0,0,0], [0,0,0,1,1,1,1], [0,0,1,1,1,1,1]] + [[1,0,1,1,1,1,1]] * 3) en retournant (2, 4) lorsqu'il y a un carré 3x3 en haut à gauche.

3 votes

Le problème fondamental est qu'il n'est pas toujours suffisant de suivre uniquement (plusieurs) rectangles de grande surface de points voisins comme vous le faites ici. Le seul algorithme O(N) que je connais et qui est correct fonctionne en itérant à travers les rangées de haut en bas, pour chaque rangée résolvant ce problème : stackoverflow.com/questions/4311694/ où les "barres" de l'"histogramme" sont constituées de toutes les traînées ascendantes ininterrompues de zéros qui commencent à la ligne actuelle (une colonne a une hauteur de 0 si elle a un 1 dans la ligne actuelle).

0 votes

@j_random_hacker : Merci pour le contre-exemple.

30voto

Sajal Jain Points 408

Veuillez jeter un coup d'œil à Maximiser la zone rectangulaire sous Histogramme puis continuez à lire la solution ci-dessous.

Traverse the matrix once and store the following;

For x=1 to N and y=1 to N    
F[x][y] = 1 + F[x][y-1] if A[x][y] is 0 , else 0

Then for each row for x=N to 1 
We have F[x] -> array with heights of the histograms with base at x.
Use O(N) algorithm to find the largest area of rectangle in this histogram = H[x]

From all areas computed, report the largest.

La complexité temporelle est de O(N*N) = O(N²) (pour une matrice binaire NxN).

Ejemplo:

Initial array    F[x][y] array
 0 0 0 0 1 0     1 1 1 1 0 1
 0 0 1 0 0 1     2 2 0 2 1 0
 0 0 0 0 0 0     3 3 1 3 2 1
 1 0 0 0 0 0     0 4 2 4 3 2
 0 0 0 0 0 1     1 5 3 5 4 0
 0 0 1 0 0 0     2 6 0 6 5 1

 For x = N to 1
 H[6] = 2 6 0 6 5 1 -> 10 (5*2)
 H[5] = 1 5 3 5 4 0 -> 12 (3*4)
 H[4] = 0 4 2 4 3 2 -> 10 (2*5)
 H[3] = 3 3 1 3 2 1 -> 6 (3*2)
 H[2] = 2 2 0 2 1 0 -> 4 (2*2)
 H[1] = 1 1 1 1 0 1 -> 4 (1*4)

 The largest area is thus H[5] = 12

0 votes

Bonne explication avec exemple

1 votes

Êtes-vous sûr que c'est O(N*N) ? Il y a deux passages sur la matrice entière, mais mon impression est que c'est O(N).

0 votes

Très belle explication.. :) J'aurais aimé que vous expliquiez aussi "Maximiser la zone rectangulaire sous l'histogramme" :D

14voto

tommy.carstensen Points 636

Voici une solution Python3, qui renvoie la position en plus de l'aire du plus grand rectangle :

#!/usr/bin/env python3

import numpy

s = '''0 0 0 0 1 0
0 0 1 0 0 1
0 0 0 0 0 0
1 0 0 0 0 0
0 0 0 0 0 1
0 0 1 0 0 0'''

nrows = 6
ncols = 6
skip = 1
area_max = (0, [])

a = numpy.fromstring(s, dtype=int, sep=' ').reshape(nrows, ncols)
w = numpy.zeros(dtype=int, shape=a.shape)
h = numpy.zeros(dtype=int, shape=a.shape)
for r in range(nrows):
    for c in range(ncols):
        if a[r][c] == skip:
            continue
        if r == 0:
            h[r][c] = 1
        else:
            h[r][c] = h[r-1][c]+1
        if c == 0:
            w[r][c] = 1
        else:
            w[r][c] = w[r][c-1]+1
        minw = w[r][c]
        for dh in range(h[r][c]):
            minw = min(minw, w[r-dh][c])
            area = (dh+1)*minw
            if area > area_max[0]:
                area_max = (area, [(r-dh, c-minw+1, r, c)])

print('area', area_max[0])
for t in area_max[1]:
    print('Cell 1:({}, {}) and Cell 2:({}, {})'.format(*t))

Sortie :

area 12
Cell 1:(2, 1) and Cell 2:(4, 4)

0 votes

Cela fonctionne très bien ! J'ai fait une version Fortran à partir de cela et je l'ai compilé pour l'utiliser en Python, car traverser un grand tableau en Python comme cela est douloureusement lent.

5voto

user984444 Points 141

Voici la méthode de J.F. Sebastians traduite en C# :

private Vector2 MaxRectSize(int[] histogram) {
        Vector2 maxSize = Vector2.zero;
        int maxArea = 0;
        Stack<Vector2> stack = new Stack<Vector2>();

        int x = 0;
        for (x = 0; x < histogram.Length; x++) {
            int start = x;
            int height = histogram[x];
            while (true) {
                if (stack.Count == 0 || height > stack.Peek().y) {
                    stack.Push(new Vector2(start, height));

                } else if(height < stack.Peek().y) {
                    int tempArea = (int)(stack.Peek().y * (x - stack.Peek().x));
                    if(tempArea > maxArea) {
                        maxSize = new Vector2(stack.Peek().y, (x - stack.Peek().x));
                        maxArea = tempArea;
                    }

                    Vector2 popped = stack.Pop();
                    start = (int)popped.x;
                    continue;
                }

                break;
            }
        }

        foreach (Vector2 data in stack) {
            int tempArea = (int)(data.y * (x - data.x));
            if(tempArea > maxArea) {
                maxSize = new Vector2(data.y, (x - data.x));
                maxArea = tempArea;
            }
        }

        return maxSize;
    }

    public Vector2 GetMaximumFreeSpace() {
        // STEP 1:
        // build a seed histogram using the first row of grid points
        // example: [true, true, false, true] = [1,1,0,1]
        int[] hist = new int[gridSizeY];
        for (int y = 0; y < gridSizeY; y++) {
            if(!invalidPoints[0, y]) {
                hist[y] = 1;
            }
        }

        // STEP 2:
        // get a starting max area from the seed histogram we created above.
        // using the example from above, this value would be [1, 1], as the only valid area is a single point.
        // another example for [0,0,0,1,0,0] would be [1, 3], because the largest area of contiguous free space is 3.
        // Note that at this step, the heigh fo the found rectangle will always be 1 because we are operating on
        // a single row of data.
        Vector2 maxSize = MaxRectSize(hist);
        int maxArea = (int)(maxSize.x * maxSize.y);

        // STEP 3:
        // build histograms for each additional row, re-testing for new possible max rectangluar areas
        for (int x = 1; x < gridSizeX; x++) {
            // build a new histogram for this row. the values of this row are
            // 0 if the current grid point is occupied; otherwise, it is 1 + the value
            // of the previously found historgram value for the previous position. 
            // What this does is effectly keep track of the height of continous avilable spaces.
            // EXAMPLE:
            //      Given the following grid data (where 1 means occupied, and 0 means free; for clairty):
            //          INPUT:        OUTPUT:
            //      1.) [0,0,1,0]   = [1,1,0,1]
            //      2.) [0,0,1,0]   = [2,2,0,2]
            //      3.) [1,1,0,1]   = [0,0,1,0]
            //
            //  As such, you'll notice position 1,0 (row 1, column 0) is 2, because this is the height of contiguous
            //  free space.
            for (int y = 0; y < gridSizeY; y++) {                
                if(!invalidPoints[x, y]) {
                    hist[y] = 1 + hist[y];
                } else {
                    hist[y] = 0;
                }
            }

            // find the maximum size of the current histogram. If it happens to be larger
            // that the currently recorded max size, then it is the new max size.
            Vector2 maxSizeTemp = MaxRectSize(hist);
            int tempArea = (int)(maxSizeTemp.x * maxSizeTemp.y);
            if (tempArea > maxArea) {
                maxSize = maxSizeTemp;
                maxArea = tempArea;
            }
        }

        // at this point, we know the max size
        return maxSize;            
    }

Quelques points à noter à ce sujet :

  1. Cette version est destinée à être utilisée avec l'API de Unity. Vous pouvez facilement la rendre plus générique en remplaçant les instances de Vector2 par KeyValuePair. Vector2 n'est utilisé que comme un moyen pratique de stocker deux valeurs.
  2. invalidPoints[] est un tableau de bool, où true signifie que le point de grille est "en service", et false signifie qu'il ne l'est pas.

3voto

Astha Gupta Points 660

Solution avec une complexité spatiale de O(colonnes) [Peut être modifié en O(lignes) également] et une complexité temporelle de O(lignes*colonnes)

public int maximalRectangle(char[][] matrix) {
    int m = matrix.length;
    if (m == 0)
        return 0;
    int n = matrix[0].length;
    int maxArea = 0;
    int[] aux = new int[n];
    for (int i = 0; i < n; i++) {
        aux[i] = 0;
    }
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            aux[j] = matrix[i][j] - '0' + aux[j];
            maxArea = Math.max(maxArea, maxAreaHist(aux));
        }
    }
    return maxArea;
}

public int maxAreaHist(int[] heights) {
    int n = heights.length;
    Stack<Integer> stack = new Stack<Integer>();
    stack.push(0);
    int maxRect = heights[0];
    int top = 0;
    int leftSideArea = 0;
    int rightSideArea = heights[0];
    for (int i = 1; i < n; i++) {
        if (stack.isEmpty() || heights[i] >= heights[stack.peek()]) {
            stack.push(i);
        } else {
            while (!stack.isEmpty() && heights[stack.peek()] > heights[i]) {
                top = stack.pop();
                rightSideArea = heights[top] * (i - top);
                leftSideArea = 0;
                if (!stack.isEmpty()) {
                    leftSideArea = heights[top] * (top - stack.peek() - 1);
                } else {
                    leftSideArea = heights[top] * top;
                }
                maxRect = Math.max(maxRect, leftSideArea + rightSideArea);
            }
            stack.push(i);
        }
    }
    while (!stack.isEmpty()) {
        top = stack.pop();
        rightSideArea = heights[top] * (n - top);
        leftSideArea = 0;
        if (!stack.isEmpty()) {
            leftSideArea = heights[top] * (top - stack.peek() - 1);
        } else {
            leftSideArea = heights[top] * top;
        }
        maxRect = Math.max(maxRect, leftSideArea + rightSideArea);
    }
    return maxRect;
}

Mais j'obtiens l'excpetion Time Limite exceeded quand j'essaie ceci sur LeetCode. Existe-t-il une solution moins complexe ?

0 votes

Simple et facile à comprendre Merci !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X