Largest Rectangle in Histogram
Un histogramme est une rangée de barres côte à côte sans espaces, chacune large d’une unité : heights[i] est la hauteur de la barre i. Un rectangle à l’intérieur couvre une série de barres voisines et ne peut pas être plus haut que la barre la plus courte de cette série.
Renvoyez la plus grande aire que peut avoir un tel rectangle.
Fonction
- heightsinteger-array
- la hauteur de chaque barre, de gauche à droite
- Renvoieinteger
- l’aire du plus grand rectangle pouvant tenir dans l’histogramme
Contraintes
1 ≤ heights.length ≤ 2 × 1040 ≤ heights[i] ≤ 105- Chaque barre a une largeur d’une unité ; un rectangle couvrant les barres
iàja donc une largeur dej-i+1unités.
Exemples
- Entrée
- heights = [2, 5, 6, 3, 4, 1]
- Sortie
- 12
- Explication
- Les quatre barres de hauteur 5, 6, 3 et 4 font toutes au moins 3 de haut, donc un rectangle de hauteur 3 les couvre : 3 × 4 = 12. Les deux barres les plus hautes, 5 et 6, ne donnent que 5 × 2 = 10.
- Entrée
- heights = [1, 8, 1, 1]
- Sortie
- 8
- Explication
- La barre de 8 seule donne 8 × 1 = 8. Tout rectangle plus large comprend une barre de 1, donc il mesure au maximum 1 × 4 = 4.
- Entrée
- heights = [3, 3, 3, 3]
- Sortie
- 12
- Explication
- Les quatre barres ont une hauteur de 3, donc tout l’histogramme forme un rectangle : 3 × 4 = 12.
+17 tests cachés à la soumission
Pour aller plus loin
Supposons que chaque barre ait sa propre largeur, indiquée dans un second tableau. Qu’est-ce qui change dans la solution en une seule passe avec une pile ?
Indices
Ouvrez-les un par un. Chacun en dévoile un peu plus.
Le plus grand rectangle touche le sommet d’au moins une barre située en dessous : sinon, tu pourrais le rendre plus haut. Essaie donc chaque barre comme barre qui détermine la hauteur. Quelle largeur peut atteindre un rectangle de cette hauteur exacte ?
Un rectangle aussi haut que la barre
is’étend vers la gauche et vers la droite jusqu’à rencontrer une barre strictement plus courte de chaque côté. Si tu connais la barre plus courte la plus proche de chaque côté pour chaque barre, chaque barre fournit une aire candidate, et il n’y en a quen.Gardez une pile d’indices dont les hauteurs augmentent du bas vers le haut. Lorsqu’une barre arrive et qu’elle n’est pas plus haute que celle au sommet, cette dernière ne peut pas s’étendre plus loin vers la droite : dépilez-la, et son rectangle couvre les barres situées strictement entre le nouvel élément au sommet de la pile et la barre actuelle. Une barre de hauteur 0 après la fin dépile tout ce qui reste.
Solution
Un rectangle peut commencer et se terminer à n’importe quelle barre, et sa hauteur dépend de la barre la plus basse qu’il couvre. Essayer chaque suite de barres coûte donc environ n²/2 étapes. Pour résoudre le problème, il faut inverser la question : le meilleur rectangle a exactement la hauteur de l’une de ses barres. Chaque barre doit donc seulement savoir jusqu’où elle peut s’étendre avant d’être arrêtée par une barre plus basse. Une pile monotone trouve ces points d’arrêt pour chaque barre, d’abord en deux parcours, puis en un seul.
Essayez chaque exécution avec un minimum courant
Correcte, mais ne termine pas sur les plus gros tests
Intuition
Un rectangle couvre une suite de barres voisines de start à end, et sa hauteur est limitée par la barre la plus basse de la suite. Il faut donc essayer chaque suite. Fixe start, puis augmente end d’une barre à la fois et conserve la hauteur minimale trouvée jusque-là. Le meilleur rectangle de cette suite a une aire de lowest × (end-start+1).
Dans [2, 5, 6, 3, 4, 1], commence par le 5. Les suites donnent 5 × 1 = 5, puis 5 × 2 = 10 avec le 6, puis 3 × 3 = 9 lorsque le 3 s’ajoute, 3 × 4 = 12 avec le 4, et 1 × 5 = 5 avec le 1. La réponse est 12. Mettre à jour lowest au fur et à mesure que la suite s’agrandit permet de garder chaque étape en O(1) ; tu n’as donc jamais à parcourir de nouveau une suite pour en trouver le minimum.
Cette méthode est correcte, car chaque rectangle se trouve au-dessus d’une suite, et pour une suite donnée, le rectangle le plus haut qui peut y tenir a exactement la hauteur de la barre la plus basse. Elle est lente, car il y a n(n+1)/2 suites : environ 2 × 10^8 pour 2 × 10^4 barres, et ce nombre ne dépend pas du tout des hauteurs. La plupart de ces suites sont limitées par une barre basse bien avant leur fin, mais la force brute continue quand même à les prolonger.
Algorithme
- Définissez
bestà 0. - Pour chaque
start, définissezlowestsurheights[start]. - Pour chaque
enddestartjusqu’à la dernière barre, abaissezlowestàheights[end]si cette barre est plus courte. - Mettez à jour
bestaveclowest × (end-start+1). - Renvoyez
best.
def largestRectangleArea(heights):
n = len(heights)
best = 0
for start in range(n):
lowest = heights[start]
for end in range(start, n):
# The rectangle over start..end is as tall as the lowest bar in it.
lowest = min(lowest, heights[end])
best = max(best, lowest * (end - start + 1))
return bestBarre la plus proche et plus courte de chaque côté
Intuition
Renversez la recherche. Dans le meilleur rectangle, au moins une barre située dessous est exactement aussi haute que le rectangle ; sinon, vous pourriez agrandir le rectangle en hauteur. La réponse est donc le meilleur rectangle, parmi toutes les barres i, d’une hauteur exactement égale à heights[i] et aussi large que possible. Il s’étend jusqu’à rencontrer, de chaque côté, une barre strictement plus basse. Appelez leurs indices left[i] et right[i], en utilisant -1 et n s’il n’y en a pas. Le rectangle couvre les barres situées strictement entre elles : sa largeur est right[i]-left[i]-1. Cela donne n candidats au lieu de n²/2.
Pour trouver left[i] pour chaque barre, parcourez le tableau de gauche à droite en conservant une pile d’indices dont les hauteurs augmentent strictement du bas vers le haut. Lorsque la barre i arrive, dépilez chaque indice dont la barre est au moins aussi haute que heights[i]. Ces barres ne peuvent jamais être la barre plus basse la plus proche à gauche de i ni d’aucune barre suivante, car i est plus proche et pas plus haut. L’indice qui reste au sommet est celui de la barre plus basse la plus proche à gauche. Empilez ensuite i. Le même parcours de droite à gauche donne right[i].
Pour [2, 5, 6, 3, 4, 1], les parcours donnent left = [-1, 0, 1, 0, 3, -1] et right = [5, 3, 3, 5, 5, 6]. La barre de hauteur 3 à l’indice 3 est bloquée par celle de hauteur 2 à l’indice 0 et celle de hauteur 1 à l’indice 5 ; son rectangle mesure donc 3 × (5-0-1) = 12. La barre de hauteur 6 est encadrée par ses voisines et ne donne que 6 × 1.
Chaque indice est empilé une fois et dépilé au plus une fois au cours de chaque parcours. Les deux parcours sont donc en O(n), même si une barre peut en dépiler beaucoup d’autres. Le coût à payer est celui de deux tableaux supplémentaires.
Algorithme
- Parcours de gauche à droite avec une pile vide. Pour chaque
i, dépile tant que la barre au sommet est au moins aussi haute queheights[i]; définisleft[i]comme le sommet, ou -1 si la pile est vide ; empilei. - Parcours de droite à gauche de la même manière pour remplir
right[i], en utilisantnsi la pile est vide. - Pour chaque
i, calculeheights[i] × (right[i]-left[i]-1). - Renvoie la plus grande de ces aires.
def largestRectangleArea(heights):
n = len(heights)
left = [-1] * n # index of the nearest shorter bar on the left, or -1
right = [n] * n # index of the nearest shorter bar on the right, or n
stack = [] # indices whose heights rise strictly from bottom to top
for i in range(n):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
left[i] = stack[-1]
stack.append(i)
stack = []
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
right[i] = stack[-1]
stack.append(i)
best = 0
for i in range(n):
# Bar i is the lowest bar of everything strictly between left[i] and right[i].
best = max(best, heights[i] * (right[i] - left[i] - 1))
return bestUn seul passage avec une pile monotone
Intuition
Le parcours de gauche à droite voit déjà chaque limite droite ; il l’ignore. Quand la barre i dépile la barre t, heights[i] n’est pas supérieure à heights[t], donc i est l’endroit où le rectangle de t s’arrête à droite. Et l’indice qui se trouve sous t dans la pile est l’endroit où il s’arrête à gauche. Il faut donc mesurer le rectangle au moment du dépilement : heights[t] × (i - below - 1), où below est le nouvel élément au sommet de la pile, ou -1 si la pile est maintenant vide.
L’invariant : les hauteurs de la pile augmentent strictement du bas vers le haut, et l’indice sous chaque entrée est la barre la plus proche à sa gauche dont la hauteur est inférieure à la sienne. Chaque barre située entre les deux a été dépilée en chemin, soit par l’entrée elle-même, soit par une barre que l’entrée a dépilée plus tard ; aucune d’elles n’est donc plus petite que l’entrée. Les barres qui ne sont jamais dépilées vont jusqu’au bout, donc après avoir traité la dernière barre, on traite une barre supplémentaire de hauteur 0. Elle est plus petite que toutes les autres et vide la pile.
Parcourons [2, 5, 6, 3, 4, 1]. Empilons 2, 5 et 6 : la pile contient les indices [0, 1, 2]. Le 3 à l’indice 3 dépile le 6 (aire 6 × (3-1-1) = 6) et le 5 (aire 5 × (3-0-1) = 10), puis s’arrête au 2 et est empilé. Empilons le 4. Le 1 à l’indice 5 dépile le 4 (aire 4), puis le 3, dont le rectangle va de l’indice 1 à l’indice 4 : 3 × (5-0-1) = 12. Il dépile aussi le 2 (2 × 5 = 10 ; la pile est vide, donc la largeur est 5). Le 0 final dépile le 1 (1 × 6 = 6). Le maximum est 12.
Dépiler sur >= signifie qu’une barre de même hauteur peut arrêter une barre prématurément. C’est sans risque : la barre de même hauteur prend sa place dans la pile, conserve la même limite gauche et, lorsqu’elle est dépilée plus tard, son rectangle couvre toute la série. Dans [3, 3, 3, 3], les trois premiers 3 enregistrent des largeurs de 1, 2 et 3, et le dernier est dépilé par le 0 final avec une largeur de 4, ce qui donne 12.
Algorithme
- Commencez avec une pile d’indices vide et
best = 0. - Pour
iallant de 0 àn, définissez la hauteur actuelle comme étantheights[i], ou 0 lorsquei = n. - Tant que la barre au sommet de la pile est au moins aussi haute que la hauteur actuelle, dépilez-la sous le nom de
t; la largeur esti - below - 1, oùbelowest le nouvel élément au sommet ou -1 ; mettez à jourbestavecheights[t] × width. - Empilez
i. - Renvoyez
best.
def largestRectangleArea(heights):
n = len(heights)
stack = [] # indices whose heights rise strictly from bottom to top
best = 0
for i in range(n + 1):
current = heights[i] if i < n else 0 # a bar of 0 past the end empties the stack
while stack and heights[stack[-1]] >= current:
# Bar i stops the popped bar on the right; the bar below it stops it on the left.
height = heights[stack.pop()]
left = stack[-1] if stack else -1
best = max(best, height * (i - left - 1))
stack.append(i)
return best
Pièges et cas limites
La boucle utilisant une pile est courte, et presque tous les bogues concernent la largeur ou les barres restantes à la fin.
- Oublier les barres encore dans la pile. Dans un histogramme croissant comme
[1, 2, 3, 4, 5], aucune barre n'est jamais dépilée dans la boucle et, sans la barre de fermeture de hauteur 0, vous renvoyez 0 au lieu de 9. - Mesurer la largeur à partir de l'indice de la barre dépilée. Son rectangle commence juste après la barre située en dessous d'elle dans la pile, et non à sa propre position : dans
[2, 5, 6, 3, 4, 1], la barre de hauteur 3 à l'indice 3 s'étend des indices 1 à 4. Utiliseri - tdonne 2 au lieu de 4. - Utiliser une largeur incorrecte lorsque la pile est vide après un dépilement. La barre dépilée est la plus basse jusqu'à présent, son rectangle remonte donc jusqu'à l'indice 0 et sa largeur est
i. Dans[2, 1, 2], la barre de hauteur 1 s'étend sur les trois barres, pour une aire de 3. - S'arrêter aux barres de hauteur égale des deux côtés dans la version en deux passages. Dans
[3, 3, 3, 3], chaque barre voit alors une largeur de 1 et vous renvoyez 3 au lieu de 12. Dépilez avec>=afin que les limites soient des barres strictement plus courtes. - Supposer que la barre la plus haute ou la plage la plus large donne le maximum. Dans
[2, 5, 6, 3, 4, 1], ni la barre de hauteur 6 ni la largeur totale de 6 barres ne donne la réponse ; c'est une hauteur moyenne sur une largeur moyenne qui l'emporte. - Débordement. Une aire atteint
10^5 × 2 × 10^4 = 2 × 10^9ici, ce qui tient encore dans un entier signé de 32 bits ; avec des limites plus grandes, effectuez la multiplication en 64 bits.
Questions fréquentes4
Quelle est la complexité temporelle du plus grand rectangle dans un histogramme ?
La solution utilisant une pile monotone s’exécute en O(n) et utilise O(n) d’espace supplémentaire. Chaque indice est empilé une fois et dépilé une fois, et chaque dépilement nécessite un nombre constant d’opérations. Essayer chaque séquence de barres prend un temps de O(n²), soit environ 2 × 10^8 opérations pour 2 × 10^4 barres.
Pourquoi le rectangle d'une barre est-il mesuré lorsqu'elle est dépilée ?
Une barre est dépilée par la première barre à sa droite qui n’est pas plus haute ; c’est donc là que son rectangle s’arrête à droite. L’indice situé en dessous d’elle dans la pile correspond à la barre moins haute la plus proche à sa gauche ; c’est donc là qu’il s’arrête à gauche. Au moment du dépilement, les deux extrémités sont connues, et l’aire est height × (i - below - 1).
Peut-on résoudre le problème du plus grand rectangle dans un histogramme par division et conquête ?
Oui. La barre la plus basse de tout l’intervalle se trouve soit sous le meilleur rectangle, qui vaut alors lowest × width, soit le divise en une partie gauche et une partie droite que vous résolvez séparément. Avec un parcours linéaire pour trouver le minimum, la complexité est O(n log n) pour une entrée aléatoire, mais O(n²) pour une entrée triée ; un arbre de segments pour les minimums sur intervalle garantit toujours une complexité de O(n log n). La pile est plus simple et plus rapide.
Comment utilise-t-on le plus grand rectangle dans un histogramme pour trouver le rectangle maximal dans une grille 0/1 ?
Parcours la grille ligne par ligne et conserve, pour chaque colonne, le nombre de 1 consécutifs se terminant à la ligne actuelle ; un 0 réinitialise ce compteur. Les compteurs de chaque ligne forment un histogramme, et le plus grand rectangle de 1 se terminant sur cette ligne est le plus grand rectangle de cet histogramme. Exécuter la pile une fois par ligne permet de résoudre la grille en O(rows × cols) temps.
Problèmes similaires
Des problèmes qui reposent sur les mêmes idées. En résoudre deux ou trois, c’est ce qui ancre un schéma.
Python
def largestRectangleArea(heights):
# Écrivez le code iciCas 1
Cas 2
Cas 3
Entrée
heights = [2, 5, 6, 3, 4, 1]
Attendu
12