Largest Rectangle in Histogram
Ein Histogramm ist eine Reihe von Balken, die ohne Lücken nebeneinanderstehen und jeweils eine Einheit breit sind: heights[i] ist die Höhe des Balkens i. Ein Rechteck darin umfasst mehrere benachbarte Balken und kann höchstens so hoch sein wie der niedrigste Balken in diesem Bereich.
Gib die größtmögliche Fläche eines solchen Rechtecks zurück.
Funktion
- heightsinteger-array
- die Höhe jedes Balkens, von links nach rechts
- Gibt zurückinteger
- die Fläche des größten Rechtecks, das in das Histogramm passt
Einschränkungen
1 ≤ heights.length ≤ 2 × 1040 ≤ heights[i] ≤ 105- Jeder Balken ist eine Einheit breit, daher ist ein Rechteck über die Balken
ibisjj-i+1Einheiten breit.
Beispiele
- Eingabe
- heights = [2, 5, 6, 3, 4, 1]
- Ausgabe
- 12
- Erklärung
- Die vier Balken 5, 6, 3 und 4 sind alle mindestens 3 hoch, daher erstreckt sich ein Rechteck mit der Höhe 3 über sie: 3 × 4 = 12. Die beiden höchsten Balken, 5 und 6, ergeben nur 5 × 2 = 10.
- Eingabe
- heights = [1, 8, 1, 1]
- Ausgabe
- 8
- Erklärung
- Der Balken aus 8 allein ergibt 8 × 1 = 8. Jedes breitere Rechteck enthält einen Balken aus 1, also ist es höchstens 1 × 4 = 4.
- Eingabe
- heights = [3, 3, 3, 3]
- Ausgabe
- 12
- Erklärung
- Alle vier Balken sind 3 hoch, also ist das gesamte Histogramm ein Rechteck: 3 × 4 = 12.
+17 versteckte Tests beim Einreichen
Weiterführende Frage
Angenommen, jeder Balken hat seine eigene Breite, die in einem zweiten Array angegeben ist. Was ändert sich an der Ein-Durchlauf-Stack-Lösung?
Tipps
Öffne sie nacheinander. Jeder verrät ein bisschen mehr.
Das größte Rechteck berührt die Oberkante mindestens eines Balkens darunter: Andernfalls könnte man es höher machen. Probiere also jeden Balken als den Balken aus, der die Höhe bestimmt. Wie breit kann ein Rechteck mit genau dieser Höhe werden?
Ein Rechteck, das so hoch ist wie der Balken
i, erstreckt sich nach links und rechts, bis es auf jeder Seite auf einen strikt niedrigeren Balken trifft. Wenn du für jeden Balken den nächstgelegenen niedrigeren Balken auf jeder Seite kennst, liefert jeder Balken eine mögliche Fläche, und es gibt nurndavon.Behalte einen Stapel von Indizes, deren Höhen von unten nach oben ansteigen. Wenn ein Balken ankommt, der nicht höher als der oberste ist, kann der oberste Balken nicht weiter nach rechts reichen: Entferne ihn vom Stapel; sein Rechteck umfasst die Balken, die strikt zwischen dem neuen obersten Element des Stapels und dem aktuellen Balken liegen. Ein Balken der Höhe 0 nach dem Ende entfernt alle noch übrigen Elemente.
Lösung
Ein Rechteck kann an jedem Balken beginnen und enden, und seine Höhe hängt vom niedrigsten Balken ab, den es umfasst. Daher kostet es etwa n²/2 Schritte, jede Balkenfolge auszuprobieren. Der Ausweg besteht darin, die Frage umzudrehen: Das beste Rechteck ist genau so hoch wie einer seiner Balken. Daher muss jeder Balken nur wissen, wie weit er sich ausdehnen kann, bevor ein niedrigerer Balken ihn aufhält. Ein monotoner Stapel findet diese Begrenzungspunkte für jeden Balken, zunächst in zwei Durchläufen und dann in einem.
Probiere jeden Durchlauf mit einem laufenden Minimum aus
Korrekt, wird aber bei den größten Tests nicht fertig
Idee
Ein Rechteck überdeckt eine Reihe benachbarter Balken von start bis end, und seine Höhe ist durch den niedrigsten Balken in der Reihe begrenzt. Probiere also jede Reihe aus. Fixiere start, vergrößere dann end Balken für Balken und behalte die bisher niedrigste Höhe bei. Die Fläche des größten Rechtecks über dieser Reihe beträgt lowest × (end-start+1).
In [2, 5, 6, 3, 4, 1] beginnst du bei der 5. Die Reihen ergeben 5 × 1 = 5, dann 5 × 2 = 10 mit der 6, dann 3 × 3 = 9, sobald die 3 dazukommt, 3 × 4 = 12 mit der 4 und 1 × 5 = 5 mit der 1. Die 12 ist die Antwort. Wenn du lowest aktualisierst, während die Reihe wächst, bleibt jeder Schritt bei O(1), sodass du die Reihe nie erneut nach ihrem Minimum durchsuchen musst.
Das Verfahren ist korrekt, weil jedes Rechteck über einer Reihe liegt und für eine feste Reihe das höchste passende Rechteck genau so hoch ist wie der niedrigste Balken. Es ist langsam, weil es n(n+1)/2 Reihen gibt: etwa 2 × 10^8 bei 2 × 10^4 Balken, und diese Anzahl hängt überhaupt nicht von den Höhen ab. Die meisten dieser Reihen werden lange vor ihrem Ende durch einen niedrigen Balken begrenzt, doch der Brute-Force-Ansatz erweitert sie trotzdem weiter.
Algorithmus
- Setze
bestauf 0. - Setze für jedes
startlowestaufheights[start]. - Senke für jedes
endvonstartbis zum letzten Balkenlowestaufheights[end], wenn dieser Balken kürzer ist. - Aktualisiere
bestmitlowest × (end-start+1). - Gib
bestzurück.
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 bestDer nächstgelegene kürzere Balken auf jeder Seite
Idee
Kehre die Suche um. Im optimalen Rechteck ist mindestens ein Balken darunter genau so hoch wie das Rechteck; andernfalls könntest du das Rechteck höher machen. Die Antwort ist also das Maximum über alle Balken i für ein Rechteck mit genau der Höhe heights[i], das sich so weit wie möglich ausdehnt. Es reicht bis zu einem auf jeder Seite strikt niedrigeren Balken. Nenne ihre Indizes left[i] und right[i]; gibt es keinen, verwende -1 beziehungsweise n. Das Rechteck umfasst die Balken strikt zwischen ihnen: Die Breite beträgt right[i]-left[i]-1. Damit gibt es n Kandidaten statt n²/2.
Um left[i] für jeden Balken zu finden, gehst du von links nach rechts und verwendest einen Stapel mit Indizes, deren Balkenhöhen von unten nach oben strikt ansteigen. Sobald Balken i erreicht wird, entfernst du alle Indizes, deren Balken mindestens so hoch ist wie heights[i]. Diese Balken können für i oder für einen späteren Balken nie der nächste niedrigere Balken sein, denn i ist näher und nicht höher. Was oben übrig bleibt, ist der nächste niedrigere Balken links davon. Dann legst du i auf den Stapel. Derselbe Durchlauf von rechts nach links ergibt right[i].
Für [2, 5, 6, 3, 4, 1] ergeben die Durchläufe left = [-1, 0, 1, 0, 3, -1] und right = [5, 3, 3, 5, 5, 6]. Der Balken mit Höhe 3 an Index 3 wird links vom Balken mit Höhe 2 an Index 0 und rechts vom Balken mit Höhe 1 an Index 5 begrenzt; sein Rechteck hat also die Fläche 3 × (5-0-1) = 12. Die 6 ist von ihren Nachbarn eingeschlossen und ergibt nur 6 × 1.
Jeder Index wird in jedem Durchlauf einmal auf den Stapel gelegt und höchstens einmal entfernt. Daher benötigen beide Durchläufe O(n), auch wenn ein Balken viele Elemente entfernen kann. Dafür sind zwei zusätzliche Arrays nötig.
Algorithmus
- Gehe von links nach rechts mit einem leeren Stapel. Entferne für jedes
iElemente, solange der Balken oben mindestens so hoch ist wieheights[i]; setzeleft[i]auf das oberste Element oder auf -1, wenn der Stapel leer ist; fügeihinzu. - Gehe auf dieselbe Weise von rechts nach links, um
right[i]zu füllen, und verwenden, wenn der Stapel leer ist. - Berechne für jedes
iheights[i] × (right[i]-left[i]-1). - Gib die größte dieser Flächen zurück.
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 bestEin Durchlauf mit einem monotonen Stack
Idee
Der Durchlauf von links nach rechts sieht bereits jede rechte Grenze, verwirft sie aber. Wenn Balken i Balken t aus dem Stapel entfernt, ist heights[i] nicht höher als heights[t], also ist i die Stelle, an der das Rechteck von t rechts endet. Und der Index, der im Stapel unter t liegt, ist die Stelle, an der es links endet. Miss also das Rechteck genau in dem Moment, in dem du den Balken entfernst: heights[t] × (i - below - 1), wobei below der neue oberste Index im Stapel ist oder -1, wenn der Stapel jetzt leer ist.
Die Invariante: Die Höhen im Stapel steigen von unten nach oben streng an, und der Index unter jedem Eintrag ist der nächstgelegene Balken links davon, der niedriger ist. Jeder Balken zwischen den beiden wurde auf dem Weg entfernt, entweder durch den Eintrag selbst oder durch einen Balken, den der Eintrag später entfernt hat. Keiner davon ist also niedriger als der Eintrag. Balken, die nie entfernt werden, reichen bis ganz ans Ende. Deshalb verarbeitest du nach dem letzten Balken noch einen Balken mit Höhe 0. Er ist niedriger als alle anderen und leert den Stapel.
Gehen wir [2, 5, 6, 3, 4, 1] durch. Füge 2, 5 und 6 hinzu: Der Stapel enthält die Indizes [0, 1, 2]. Die 3 am Index 3 entfernt die 6 (Fläche 6 × (3-1-1) = 6) und die 5 (Fläche 5 × (3-0-1) = 10), hält dann bei der 2 an und wird hinzugefügt. Füge die 4 hinzu. Die 1 am Index 5 entfernt die 4 (Fläche 4), dann die 3, deren Rechteck von Index 1 bis 4 reicht: 3 × (5-0-1) = 12. Sie entfernt auch die 2 (2 × 5 = 10; der Stapel ist leer, also beträgt die Breite 5). Die abschließende 0 entfernt die 1 (1 × 6 = 6). Das Maximum ist 12.
Wenn beim Entfernen >= verwendet wird, kann ein gleich hoher Balken einen anderen Balken zu früh stoppen. Das ist unproblematisch: Der gleich hohe Balken nimmt seinen Platz im Stapel ein, übernimmt dieselbe linke Grenze und umfasst später, wenn er entfernt wird, den gesamten Abschnitt. Bei [3, 3, 3, 3] ergeben die ersten drei 3en die Breiten 1, 2 und 3; die letzte wird von der abschließenden 0 mit Breite 4 entfernt, was 12 ergibt.
Algorithmus
- Beginne mit einem leeren Stapel von Indizes und
best = 0. - Für
ivon 0 bisnsei die aktuelle Höheheights[i]oder 0, wenni = n. - Solange der Balken oben auf dem Stapel mindestens so hoch ist wie die aktuelle Höhe, nimm ihn als
tvom Stapel; die Breite isti - below - 1, wobeibelowdas neue oberste Element oder -1 ist; aktualisierebestmitheights[t] × width. - Lege
iauf den Stapel. - Gib
bestzurück.
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
Stolperfallen und Grenzfälle
Die Stack-Schleife ist kurz, und fast jeder Fehler steckt in der Breite oder in den Balken, die am Ende noch übrig sind.
- Die noch auf dem Stack liegenden Balken vergessen. Bei einem ansteigenden Histogramm wie
[1, 2, 3, 4, 5]wird innerhalb der Schleife nie etwas entfernt. Ohne den abschließenden Balken der Höhe 0 erhältst du 0 statt 9. - Die Breite anhand des eigenen Index des entfernten Balkens messen. Sein Rechteck beginnt direkt nach dem Balken darunter auf dem Stack, nicht bei ihm selbst: In
[2, 5, 6, 3, 4, 1]erstreckt sich die 3 an Index 3 über die Indizes 1 bis 4. Miti - terhältst du 2 statt 4. - Die falsche Breite verwenden, wenn der Stack nach dem Entfernen leer ist. Der entfernte Balken ist bisher der niedrigste, daher reicht sein Rechteck bis zurück zu Index 0, und die Breite ist
i. In[2, 1, 2]erstreckt sich die 1 über alle drei Balken und ergibt eine Fläche von 3. - In der Version mit zwei Durchläufen bei gleichen Balken auf beiden Seiten anhalten. Bei
[3, 3, 3, 3]sieht dann jeder Balken eine Breite von 1, und du erhältst 3 statt 12. Entferne bei>=, damit die Grenzen durch strikt kürzere Balken gebildet werden. - Annehmen, dass der höchste Balken oder die breiteste Spanne gewinnt. In
[2, 5, 6, 3, 4, 1]ergibt weder die 6 noch die volle Breite von 6 Balken die Antwort; entscheidend ist eine mittlere Höhe über einer mittleren Breite. - Überlauf. Eine Fläche erreicht hier
10^5 × 2 × 10^4 = 2 × 10^9, was noch in eine vorzeichenbehaftete 32-Bit-Ganzzahl passt. Bei größeren Grenzwerten solltest du in 64-Bit multiplizieren.
Häufige Fragen4
Wie hoch ist die Zeitkomplexität von „Largest Rectangle in Histogram“?
Die Lösung mit dem monotonen Stapel benötigt O(n) Zeit und O(n) zusätzlichen Speicherplatz. Jeder Index wird einmal auf den Stapel gelegt und einmal wieder entfernt, und jedes Entfernen erfordert eine konstante Anzahl von Arbeitsschritten. Alle möglichen zusammenhängenden Balkenfolgen auszuprobieren, benötigt O(n²) Zeit, etwa 2 × 10^8 Schritte für 2 × 10^4 Balken.
Warum wird das Rechteck eines Balkens gemessen, wenn er herausgenommen wird?
Ein Balken wird durch den ersten Balken rechts von ihm entfernt, der nicht höher ist; dort endet sein Rechteck also auf der rechten Seite. Der Index darunter im Stapel bezeichnet den nächsten kürzeren Balken links von ihm; dort endet es auf der linken Seite. Im Moment des Entfernens sind beide Enden bekannt, und die Fläche ist height × (i - below - 1).
Kann das größte Rechteck im Histogramm mit Teile und herrsche gelöst werden?
Ja. Der niedrigste Balken im gesamten Bereich liegt entweder unter dem besten Rechteck, dessen Fläche dann lowest × width beträgt, oder teilt den Bereich in einen linken und einen rechten Teil, die du jeweils separat löst. Bei einer linearen Suche nach dem Minimum beträgt die Laufzeit bei zufälligen Eingaben O(n log n), bei sortierten Eingaben jedoch O(n²); mit einem Segmentbaum für Bereichsminima beträgt sie immer O(n log n). Der Stack ist einfacher und schneller.
Wie wird das größte Rechteck im Histogramm für das maximale Rechteck in einem 0/1-Raster verwendet?
Gehe das Raster Zeile für Zeile durch und speichere für jede Spalte, wie viele 1en hintereinander bis zur aktuellen Zeile stehen; eine 0 setzt diesen Zähler zurück. Die Zähler jeder Zeile bilden ein Histogramm, und das größte Rechteck aus 1en, das in dieser Zeile endet, ist das größte Rechteck in diesem Histogramm. Wenn du den Stack einmal pro Zeile durchläufst, löst du das Problem für das Raster in O(rows × cols) Zeit.
Ähnliche Aufgaben
Aufgaben mit denselben Ideen. Wer zwei oder drei davon löst, behält das Muster.
Python
def largestRectangleArea(heights):
# Schreibe hier den CodeFall 1
Fall 2
Fall 3
Eingabe
heights = [2, 5, 6, 3, 4, 1]
Erwartet
12