77 votes

Agrandir la zone rectangulaire sous l'histogramme

J'ai un histogramme avec entier hauteurs et de largeur constante 1. Je veux maximiser la zone rectangulaire en vertu d'un histogramme. par exemple:

 _
| |
| |_ 
|   |
|   |_
|     |

La réponse à cette question serait de 6, 3 * 2, à l'aide de col1 et col2.

O(n^2) que la force brute est clair pour moi, je voudrais un O(n log n) de l'algorithme. Je vais essayer de penser la programmation dynamique le long de la lignes de maximum de l'augmentation de la O(n log n) algo, mais je ne suis pas aller de l'avant. Dois-je utiliser diviser et conquérir de l'algorithme?

PS: les Gens avec assez de réputation sont invités à supprimer les diviser et conquérir si la balise n'est pas la solution.

Après des mutuelles commentaires: je veux dire la zone de plus grand rectangle qui correspond entièrement. (Merci j_random_hacker pour clarifier :) ).

50voto

ChuanRocks Points 471

Il y a trois manières de résoudre ce problème en plus de l'approche par force brute. Je vais écrire tous les d'eux. Les codes java ont passé les tests en ligne juge du site appelé leetcode: http://www.leetcode.com/onlinejudge#question_84. je suis donc persuadé codes sont corrects.

Solution 1: la programmation dynamique + n*n de la matrice en tant que cache de

temps: O(n^2), de l'espace: O(n^2)

Idée de base: utiliser le n*n de la matrice dp[i][j] à cache la hauteur minimale entre bar[i] et bar[j]. Commencez le remplissage de la matrice des rectangles de largeur 1.

public int solution1(int[] height) {

    int n = height.length;
    if(n == 0) return 0;
    int[][] dp = new int[n][n];        
    int max = Integer.MIN_VALUE;

    for(int width = 1; width <= n; width++){

        for(int l = 0; l+width-1 < n; l++){

            int r = l + width - 1;

            if(width == 1){
                dp[l][l] = height[l];
                max = Math.max(max, dp[l][l]);
            } else {                    
                dp[l][r] = Math.min(dp[l][r-1], height[r]);
                max = Math.max(max, dp[l][r] * width);
            }                
        }
    }

    return max;
}

Solution 2: la programmation dynamique + 2 tableaux comme cache.

temps: O(n^2), de l'espace: O(n)

Idée de base: cette solution comme la solution 1, mais permet d'économiser de l'espace. L'idée est que dans la solution 1 nous construisons la matrice à partir de la ligne 1 à la ligne n. Mais à chaque itération, seule la rangée précédente contribue à la construction de la ligne actuelle. Nous utilisons donc deux tableaux en rangée précédente et de l'actuelle ligne à tour de rôle.

public int Solution2(int[] height) {

    int n = height.length;
    if(n == 0) return 0;

    int max = Integer.MIN_VALUE;

    // dp[0] and dp[1] take turns to be the "previous" line.
    int[][] dp = new int[2][n];      

    for(int width = 1; width <= n; width++){

        for(int l = 0; l+width-1 < n; l++){

            if(width == 1){
                dp[width%2][l] = height[l];
            } else {
                dp[width%2][l] = Math.min(dp[1-width%2][l], height[l+width-1]);                     
            }
            max = Math.max(max, dp[width%2][l] * width);   
        }
    }        
    return max;
}

Solution 3: utilisation de la pile.

temps: O(n), l'espace:O(n)

Cette solution est délicate et j'ai appris à le faire à partir de l'explication, sans graphiques et d'explication avec les graphiques. Je vous suggère de lire les deux liens avant de lire mon explication ci-dessous. C'est difficile à expliquer sans graphiques si mes explications pourrait être difficile à suivre.

Voici mes explications:

  1. Pour chaque barre, nous devons être en mesure de trouver le plus grand rectangle contenant ce bar. De sorte que le plus grand de ces n rectangles est ce que nous voulons.

  2. Pour obtenir le plus grand rectangle pour une barre (disons bar[j'ai], le (i+1)ème bar), nous avons juste besoin de trouver le plus grand intervalle de que contient cette barre. Ce que nous savons, c'est que tous les bars, dans cet intervalle doit être au moins à la même hauteur avec barre[i]. Donc, si nous avons à comprendre comment beaucoup de consécutifs de même hauteur ou supérieur bars sont là, sur la gauche de la barre[i], et combien consécutifs de même hauteur ou supérieur bars sont là, sur la droite de la barre[i], nous permettra de connaître la longueur de l'intervalle, qui est la largeur du plus grand rectangle de la barre[i].

  3. Pour compter le nombre de jours de même hauteur ou supérieur barres sur la gauche de la barre[i], nous avons seulement besoin de trouver la plus proche de bar sur la gauche qui est plus courte que la barre[i], parce que tous les bars entre cette barre et barre[i] sera consécutifs de même hauteur ou supérieur bars.

  4. Nous utilisons une pile à dynamicly garder la trace de tous à la barre de gauche qui sont plus courts que d'un certain bar. En d'autres termes, si nous itération à partir de la première barre à la barre[i], quand on vient d'arriver à la barre[i] et que vous n'avez pas mis à jour de la pile, la pile doit stocker toutes les barres qui ne sont pas plus élevés qu'à la mesure[i-1], y compris bar[i-1] en lui-même. Nous comparons bar[i] hauteur de chaque barre dans la pile jusqu'à ce que nous en trouver un qui est plus courte que la barre[i], qui est le cloest plus courte barre. Si la barre[i] est plus élevé que tous les bars de la pile, cela signifie que toutes les barres sur la gauche de la barre[i] sont plus élevés que la barre[i].

  5. Nous pouvons faire la même chose sur le côté droit de la i-ème de la barre. Alors que nous savons pour bar[i] comment de nombreux bars sont là, dans l'intervalle.

    public int solution3(int[] height) {
    
        int n = height.length;
        if(n == 0) return 0;
    
        Stack<Integer> left = new Stack<Integer>();
        Stack<Integer> right = new Stack<Integer>();
    
        int[] width = new int[n];// widths of intervals.
        Arrays.fill(width, 1);// all intervals should at least be 1 unit wide.
    
        for(int i = 0; i < n; i++){
            // count # of consecutive higher bars on the left of the (i+1)th bar
            while(!left.isEmpty() && height[i] <= height[left.peek()]){
                // while there are bars stored in the stack, we check the bar on the top of the stack.
                left.pop();                
            }
    
            if(left.isEmpty()){
                // all elements on the left are larger than height[i].
                width[i] += i;
            } else {
                // bar[left.peek()] is the closest shorter bar.
                width[i] += i - left.peek() - 1;
            }
            left.push(i);
        }
    
        for (int i = n-1; i >=0; i--) {
    
            while(!right.isEmpty() && height[i] <= height[right.peek()]){                
                right.pop();                
            }
    
            if(right.isEmpty()){
                // all elements to the right are larger than height[i]
                width[i] += n - 1 - i;
            } else {
                width[i] += right.peek() - i - 1;
            }
            right.push(i);
        }
    
        int max = Integer.MIN_VALUE;
        for(int i = 0; i < n; i++){
            // find the maximum value of all rectangle areas.
            max = Math.max(max, width[i] * height[i]);
        }
    
        return max;
    }
    

17voto

IVlad Points 20932

Vous avez ici beaucoup de solutions, à la fois O(n log n) et O(n) .

15voto

J.F. Sebastian Points 102961

Implémentation en Python de la @IVlad la réponse de O(n) solution:

from collections import namedtuple

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

def max_rectangle_area(histogram):
    """Find the area of the largest rectangle that fits entirely under
    the histogram.

    """
    stack = []
    top = lambda: stack[-1]
    max_area = 0
    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_area = max(max_area, top().height*(pos-top().start))
                start, _ = stack.pop()
                continue
            break # height == top().height goes here

    pos += 1
    for start, height in stack:
        max_area = max(max_area, height*(pos-start))

    return max_area

Exemple:

>>> f = max_rectangle_area
>>> f([5,3,1])
6
>>> f([1,3,5])
6
>>> f([3,1,5])
5
>>> f([4,8,3,2,0])
9
>>> f([4,8,3,1,1,0])
9

La recherche linéaire à l'aide d'une pile de sous-problèmes incomplètes

Copier-coller de l'algorithme de description (dans le cas de la page descend):

Nous traitons les éléments de la de gauche à droite et de maintenir un la pile de l'information au sujet démarré, mais encore inachevée subhistograms. Chaque fois que un élément nouveau arrive, il est soumis les règles suivantes. Si la pile est vide, nous ouvrons une nouvelle subproblem par en poussant l'élément sur la pile. Sinon on la compare à celle de l'élément en haut de la pile. Si la nouvelle est plus nous avons de nouveau pousser. Si la nouvelle l'un est l'égalité de nous ignorer. Dans tous ces cas, nous continuons avec la nouvelle de l'élément. Si la nouvelle est moins, nous terminer le plus haut subproblem par la mise à jour de la superficie maximale w.r.t. l' l'élément au sommet de la pile. Ensuite, nous rejetons l'élément en haut, et répétez la procédure de maintien de la nouvel élément. De cette façon, tous les sous-problèmes sont finis jusqu'à ce que le la pile est vide, ou ses l'élément est inférieure ou égale à la élément nouveau, conduisant à des actions décrit ci-dessus. Si tous les éléments ont été traitées, et la pile n'est pas encore vide, nous avons fini le reste sous-problèmes en mettant à jour le maximum la zone w.r.t. pour les éléments à l' haut.

Pour la mise à jour de w.r.t. un élément, nous trouver le plus grand rectangle qui comprend l'élément. Observer qu'une mise à jour de la superficie maximale est assurée pour tous les éléments à l'exception de ceux sauté. Si un élément est ignoré, cependant, il a le même plus grand rectangle comme l'élément en haut de la pile à ce moment que sera mis à jour plus tard. La hauteur de la plus grand rectangle est, bien sûr, la la valeur de l'élément. Au moment de la la mise à jour, nous savons dans quelle mesure l' plus grand rectangle s'étend vers la droite de l'élément, parce qu'alors, pour la première fois, un nouvel élément avec le plus petit la hauteur est arrivé. L'information, comment de loin le plus grand rectangle s'étend à la gauche de l'élément, est disponible si nous conservons sur la pile, trop.

Nous avons donc réviser la procédure décrit ci-dessus. Si un nouvel élément est poussé immédiatement, soit parce que la la pile est vide ou qu'il est supérieur à l'élément supérieur de la pile, la plus grand rectangle contenant s'étend vers la gauche pas plus loin que l'élément en cours. S'il est poussé après plusieurs éléments ont été sauté hors de la pile, parce que c'est moins de ces éléments, le plus grand rectangle contenant elle s'étend à l' gauche aussi loin que celle de la plupart des récemment sauté élément.

Chaque élément est poussé et a sauté à plus d'une fois et à chaque étape de la procédure au moins un élément est poussé ou de maïs. Étant donné que le montant de de travail pour les décisions et la mise à jour est constante, la complexité de la l'algorithme est O(n) par amorti de l'analyse.

5voto

Eduard Rostomyan Points 636

La solution la plus simple en O (N)

 long long getMaxArea(long long hist[], long long n)
{

    stack<long long> s;

    long long max_area = 0; 
    long long tp;  
    long long area_with_top; 

    long long i = 0;
    while (i < n)
    {
        if (s.empty() || hist[s.top()] <= hist[i])
            s.push(i++);
       else
        {
            tp = s.top();  // store the top index
            s.pop();  // pop the top
            area_with_top = hist[tp] * (s.empty() ? i : i - s.top() - 1);
            if (max_area < area_with_top)
            {
                max_area = area_with_top;
            }
        }
    }

   while (!s.empty())
    {
        tp = s.top();
        s.pop();
        area_with_top = hist[tp] * (s.empty() ? i : i - s.top() - 1);

        if (max_area < area_with_top)
            max_area = area_with_top;
    }

    return max_area;
}
 

1voto

Aaron Watters Points 1000

Je ne comprends pas les autres entrées, mais je pense que je sais comment le faire en O(n) comme suit.

A) pour chaque indice de trouver le plus grand rectangle à l'intérieur de l'histogramme se terminant à l'indice de l'endroit où l'indice de la colonne touche le haut du rectangle et rappelez-vous où le rectangle commence. Cela peut être fait en O(n) à l'aide d'une pile en fonction de l'algorithme.

B) de Même, pour chaque indice de trouver le plus grand rectangle de départ à l'index où la colonne d'index touche le haut du rectangle et rappelez-vous où le rectangle se termine. Aussi O(n) en utilisant la même méthode que (Un) mais l'analyse de l'histogramme vers l'arrière.

C) Pour chaque indice de combiner les résultats de (A) et (B) pour déterminer le plus grand rectangle où la colonne à l'index touche le haut du rectangle. O(n) comme (Un).

D) Depuis le plus grand rectangle doit être touché par certaines colonnes de l'histogramme le plus grand rectangle est le plus grand rectangle trouvé dans l'étape (C).

La partie la plus difficile est la mise en œuvre (A) et (B), qui, je pense, est ce que JF Sebastian peuvent avoir résolu plutôt que le problème général a déclaré.

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