Größtes Rechteck im Histogramm
Verwenden Sie einen monotonen Stack, um linke Grenzen zu verfolgen und die maximale Rechtecksfläche innerhalb eines Histogramms in einem einzigen Durchlauf zu berechnen.
Größtes Rechteck im Histogramm ist eine kostenlose Coding Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Coding Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Problem: Größtes Rechteck in einem Histogramm
Beim Problem Largest Rectangle in Histogram (LeetCode 84) erhalten Sie ein Array nichtnegativer Ganzzahlen, die die Höhen von Balken in einem Histogramm darstellen. Jeder Balken hat die Breite 1. Finden Sie die Fläche des größten Rechtecks, das innerhalb des Histogramms gebildet werden kann. Das Rechteck muss zusammenhängende Balken umfassen, und seine Höhe ist durch den kürzesten enthaltenen Balken begrenzt.
Ein Brute-Force-Ansatz: Berechnen Sie für jedes Paar (i, j) die minimale Höhe im Bereich [i, j] und multiplizieren Sie sie mit (j - i + 1). Das ergibt O(n³) oder O(n²) mit vorberechneten Minima – und ist zu langsam. Die Lösung mit einem monotonen Stack läuft in O(n).
# Example: heights = [2, 1, 5, 6, 2, 3]
# Rectangles:
# width=1, height=6 at index 3 => area=6
# width=2, height=5 at indices 2-3 => area=10 (maximum!)
# width=6, height=1 across all => area=6
# width=3, height=2 at indices 2-4 => area=6
heights = [2, 1, 5, 6, 2, 3]
print('Heights:', heights)
print('Expected max area: 10 (bars of height 5 and 6, width 2)')
# Brute force for small inputs:
def brute_force(heights):
n = len(heights)
max_area = 0
for i in range(n):
min_h = heights[i]
for j in range(i, n):
min_h = min(min_h, heights[j])
max_area = max(max_area, min_h * (j - i + 1))
return max_area
print('Brute force answer:', brute_force(heights)) # 10Zentrale Erkenntnis: Was begrenzt das Rechteck eines Balkens?
Für jeden Balken i mit der Höhe h reicht das größte Rechteck, bei dem dieser Balken das Minimum bildet, nach links bis zum ersten Balken, der kleiner als h ist, und nach rechts ebenfalls bis zum ersten kürzeren Balken. Die Breite ist right_boundary - left_boundary - 1, und die Fläche ist h × width.
Damit lässt sich das Problem neu formulieren: Für jeden Balken werden das vorherige kleinere Element (PSE) und das nächstkleinere Element (NSE) gesucht. Genau diese Werte berechnet ein monoton aufsteigender Stack. In dem Moment, in dem Balken i entfernt wird (weil ein kürzerer Balken gefunden wurde), ist der aktuelle Balken sein NSE, und das oberste Element des Stacks nach dem Entfernen ist sein PSE.
heights = [2, 1, 5, 6, 2, 3]
n = len(heights)
# Find PSE and NSE for each bar
pse = [-1] * n # index of previous smaller element
nse = [n] * n # index of next smaller element (default: beyond array)
# PSE
stack = []
for i in range(n):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
pse[i] = stack[-1] if stack else -1
stack.append(i)
# NSE
stack = []
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
nse[i] = stack[-1] if stack else n
stack.append(i)
max_area = 0
for i in range(n):
width = nse[i] - pse[i] - 1
area = heights[i] * width
print(f'Bar {i} (h={heights[i]}): PSE={pse[i]}, NSE={nse[i]}, width={width}, area={area}')
max_area = max(max_area, area)
print('Max area:', max_area)Lösung in einem Durchlauf mit einem monotonen Stack
Der oben beschriebene Ansatz mit zwei Durchläufen funktioniert, kann aber zu einem einzigen Durchlauf zusammengeführt werden. Verarbeiten Sie die Balken von links nach rechts mit einem monoton aufsteigenden Stack. Wenn Balken i kürzer als das oberste Stack-Element ist, entfernen Sie dieses oberste Element. Die Höhe des entfernten Balkens ist die Höhe eines Rechtecks, seine rechte Begrenzung ist i, und seine linke Begrenzung ist das neue oberste Stack-Element + 1.
Ein üblicher Trick besteht darin, am Ende von heights einen Sentinel-Wert 0 anzuhängen. Dadurch werden am Ende alle Balken aus dem Stack entfernt, selbst wenn auf natürliche Weise kein kürzerer Balken mehr erscheint. Ohne den Sentinel benötigen Sie nach der Schleife eine Bereinigung der verbleibenden Stack-Elemente.
def largest_rectangle(heights):
stack = [] # monotonic increasing: indices of bars
max_area = 0
heights = heights + [0] # sentinel: forces all bars to be popped
for i, h in enumerate(heights):
while stack and heights[stack[-1]] > h:
height = heights[stack.pop()] # height of the rectangle
width = i if not stack else i - stack[-1] - 1 # left boundary
max_area = max(max_area, height * width)
stack.append(i)
return max_area
print(largest_rectangle([2, 1, 5, 6, 2, 3])) # 10
print(largest_rectangle([2, 4])) # 4
print(largest_rectangle([1, 1])) # 2
print(largest_rectangle([0, 9])) # 9
print(largest_rectangle([6, 7, 5, 2, 4, 5, 9, 3])) # 16Ablauf des Algorithmus in einem Durchlauf
Sehen wir uns [2, 1, 5, 6, 2, 3, 0] (mit Sentinel) Schritt für Schritt an:
- i=0, h=2: 0 wird hinzugefügt. Stack: [0]
- i=1, h=1: 0 wird entfernt (h=2, width=1, area=2). Stack ist leer, 1 wird hinzugefügt. Stack: [1]
- i=2, h=5: 5>1, 2 wird hinzugefügt. Stack: [1,2]
- i=3, h=6: 6>5, 3 wird hinzugefügt. Stack: [1,2,3]
- i=4, h=2: 3 wird entfernt (h=6,width=4-2-1=1,area=6), 2 wird entfernt (h=5,width=4-1-1=2,area=10★), 2>1, daher Stopp. 4 wird hinzugefügt. Stack: [1,4]
- i=5, h=3: 3>2, 5 wird hinzugefügt. Stack: [1,4,5]
- i=6, Sentinel h=0: Alle Elemente werden entfernt und die Flächen berechnet …
def largest_rectangle_trace(heights):
stack = []
max_area = 0
hs = heights + [0]
for i, h in enumerate(hs):
while stack and hs[stack[-1]] > h:
top = stack.pop()
w = i if not stack else i - stack[-1] - 1
area = hs[top] * w
print(f' Pop bar {top} (h={hs[top]}): width={w}, area={area}', end='')
if area > max_area:
max_area = area
print(' *** NEW MAX ***', end='')
print()
print(f'i={i} h={h}: push {i}, stack={[hs[s] for s in stack + [i]]}')
stack.append(i)
print(f'Max area: {max_area}')
return max_area
largest_rectangle_trace([2, 1, 5, 6, 2, 3])Breitenberechnung: Warum i - stack[-1] - 1?
Wenn wir Balken j aus dem Stack entfernen, wissen wir: Die rechte Begrenzung des Rechtecks von j ist i (der erste rechts von j liegende Balken, der kürzer ist). Die linke Begrenzung ist der Balken, der nach dem Entfernen unmittelbar unter j im Stack liegt – nennen wir ihn k. Die Breite beträgt daher i - k - 1 (die Balken von k+1 bis einschließlich i-1).
Ist der Stack nach dem Entfernen leer, erstreckt sich das Rechteck von j bis zum linken Rand (Index 0). Die Breite beträgt einfach i (die Indizes 0 bis i-1, deren Balken alle mindestens so hoch wie heights[j] sind). Dies ist der Sonderfall width = i if not stack else i - stack[-1] - 1.
# Illustrating left/right boundary logic
heights = [1, 3, 5, 2]
# After processing with stack:
# When we pop bar 2 (h=5) at i=3 (h=2):
# stack after pop = [0, 1] => left boundary = 1+1=2, right=3-1=2 => width=1
# When we pop bar 1 (h=3) at i=3 (h=2):
# stack after pop = [0] => left boundary = 0+1=1, right=3-1=2 => width=2
# etc.
def compute_boundaries(heights):
hs = heights + [0]
stack = []
for i, h in enumerate(hs):
while stack and hs[stack[-1]] > h:
top = stack.pop()
if stack:
left = stack[-1] + 1
width = i - stack[-1] - 1
else:
left = 0
width = i
print(f'Bar {top} (h={hs[top]}): extends from {left} to {i-1}, width={width}')
stack.append(i)
compute_boundaries([2, 1, 5, 6, 2, 3])Größtes Rechteck in einer binären Matrix
Maximal Rectangle (LeetCode 85) erweitert das Histogrammproblem auf eine zweidimensionale binäre Matrix. Berechnen Sie für jede Zeile die Höhe der aufeinanderfolgenden 1en über jeder Zelle. Dadurch entsteht für diese Zeile ein Histogramm. Wenden Sie auf das Histogramm jeder Zeile den Algorithmus für das größte Rechteck in einem Histogramm an. Das globale Maximum über alle Zeilen ist die Antwort.
Damit wird ein zweidimensionales Problem auf n wiederholte eindimensionale Histogrammprobleme reduziert. Die Zeitkomplexität beträgt O(m × n) für eine Matrix mit m Zeilen und n Spalten – ein Histogrammdurchlauf pro Zeile, wobei jeder Durchlauf O(n) benötigt.
def maximal_rectangle(matrix):
if not matrix or not matrix[0]:
return 0
n = len(matrix[0])
heights = [0] * n
max_area = 0
def hist_max_area(h):
stack, area = [], 0
for i, hh in enumerate(h + [0]):
while stack and h[stack[-1]] > hh:
top = stack.pop()
w = i if not stack else i - stack[-1] - 1
area = max(area, h[top] * w)
stack.append(i)
return area
for row in matrix:
for j in range(n):
heights[j] = heights[j] + 1 if row[j] == '1' else 0
max_area = max(max_area, hist_max_area(heights[:]))
return max_area
matrix = [['1','0','1','0','0'],
['1','0','1','1','1'],
['1','1','1','1','1'],
['1','0','0','1','0']]
print(maximal_rectangle(matrix)) # 6Sonderfälle bei Histogrammproblemen
Wichtige Sonderfälle, die Sie berücksichtigen sollten:
- Alle Höhen gleich: Das gesamte Array bildet ein Rechteck; Ergebnis = n × height
- Monoton aufsteigend: Bis zum Sentinel wird kein Element entfernt; die Fläche des letzten Balkens ist das Maximum
- Einzelner Balken: Ergebnis = height[0]
- Balken mit Höhe 0: Sie wirken als natürliche Sentinel-Werte und teilen das Histogramm in unabhängige Abschnitte
Der Sentinel (das Anhängen von 0) am Ende behandelt den monoton aufsteigenden Fall, indem er erzwingt, dass am Ende alle verbleibenden Balken entfernt werden. Ohne ihn benötigen Sie nach der Hauptiteration eine separate Bereinigungsschleife.
def largest_rectangle(heights):
stack = []
max_area = 0
heights = heights + [0]
for i, h in enumerate(heights):
while stack and heights[stack[-1]] > h:
top = stack.pop()
w = i if not stack else i - stack[-1] - 1
max_area = max(max_area, heights[top] * w)
stack.append(i)
return max_area
# Edge cases
print(largest_rectangle([5, 5, 5, 5])) # 20 (all same)
print(largest_rectangle([1, 2, 3, 4, 5])) # 9 (increasing: 3*3)
print(largest_rectangle([5, 4, 3, 2, 1])) # 9 (decreasing: 3*3)
print(largest_rectangle([5])) # 5 (single bar)
print(largest_rectangle([0, 0, 0])) # 0 (all zero)
print(largest_rectangle([3, 0, 3])) # 3 (zero splits)Alternative: Teile und herrsche
Das Histogrammproblem kann auch mit Teile und herrsche gelöst werden: Teilen Sie am Balken mit der minimalen Höhe, lösen Sie beide Hälften rekursiv und vergleichen Sie das Ergebnis mit dem Rechteck über die gesamte Breite, dessen Höhe der minimalen Höhe entspricht. Dies ergibt im Durchschnitt O(n log n), im schlechtesten Fall bei sortierten Eingaben jedoch O(n²).
Der Ansatz mit einem monotonen Stack ist mit O(n) im schlechtesten Fall eindeutig besser. Das Verständnis des Teile-und-herrsche-Ansatzes vertieft jedoch die Intuition für das Problem und erklärt, warum der Balken mit der minimalen Höhe in jedem Abschnitt stets der begrenzende Faktor für Rechtecke über die gesamte Breite ist.
def largest_rectangle_dc(heights, lo=0, hi=None):
if hi is None:
hi = len(heights) - 1
if lo > hi:
return 0
# Find the index of the minimum height in [lo, hi]
min_idx = lo
for i in range(lo, hi + 1):
if heights[i] < heights[min_idx]:
min_idx = i
# Three options:
# 1. Max rect entirely in left half
# 2. Max rect entirely in right half
# 3. Max rect spanning entire [lo, hi] with height = min
full_width_area = heights[min_idx] * (hi - lo + 1)
left_area = largest_rectangle_dc(heights, lo, min_idx - 1)
right_area = largest_rectangle_dc(heights, min_idx + 1, hi)
return max(full_width_area, left_area, right_area)
print(largest_rectangle_dc([2, 1, 5, 6, 2, 3])) # 10Histogramm-Muster: Anzahl der Teilarrays
Ein verwandtes Problem mit derselben Stack-Technik besteht darin, die Anzahl der Teilarrays in einem Histogramm zu zählen, deren kleinstes Element einem bestimmten Zielwert entspricht. Dazu werden für jeden Balken PSE und NSE berechnet und anschließend die Formel (i - pse[i]) × (nse[i] - i) verwendet. Sie zählt die Teilhistogramme, in denen Balken i das Minimum ist.
Diese Technik „Anzahl links × Anzahl rechts“ kommt in mehreren LeetCode-Problemen vor: Summe der Minima von Teilarrays (907), Anzahl von Teilstrings mit ausschließlich eindeutigen Zeichen und Probleme mit Beitragsberechnung. Der monotone Stack berechnet PSE und NSE in O(n) und ermöglicht dadurch einen Beitrag pro Element in O(1).
def sum_of_subarray_minimums(arr):
n = len(arr)
pse = [-1] * n # previous strictly smaller element
nse = [n] * n # next smaller or equal element
stack = []
for i in range(n):
while stack and arr[stack[-1]] >= arr[i]:
stack.pop()
pse[i] = stack[-1] if stack else -1
stack.append(i)
stack = []
for i in range(n - 1, -1, -1):
while stack and arr[stack[-1]] > arr[i]:
stack.pop()
nse[i] = stack[-1] if stack else n
stack.append(i)
MOD = 10**9 + 7
total = 0
for i in range(n):
left_count = i - pse[i] # subarrays where i is leftmost min
right_count = nse[i] - i # subarrays where i is the min
total += arr[i] * left_count * right_count
return total % MOD
print(sum_of_subarray_minimums([3, 1, 2, 4])) # 17
print(sum_of_subarray_minimums([11, 81, 94, 43, 3])) # 444Praktische Tipps für Vorstellungsgespräche
Wenn Sie in einem Vorstellungsgespräch auf ein Histogrammproblem stoßen, gehen Sie diese Checkliste durch:
- Klären Sie: Dürfen Höhen 0 sein? Was soll ausgegeben werden – Fläche, Indizes oder Anzahl?
- Beginnen Sie mit Brute Force und nennen Sie die Komplexität O(n²) oder O(n³)
- Erwähnen Sie, dass der Beitrag jedes Balkens von seiner Ausdehnung nach links und rechts bis zum jeweils nächsten kürzeren Balken abhängt
- Führen Sie PSE/NSE → monotonen Stack → O(n)-Lösung ein
- Verwenden Sie den Sentinel-Trick (0 anhängen), um den Code zu vereinfachen
- Verfolgen Sie ein kleines Beispiel am Whiteboard
Eine häufige Anschlussfrage lautet: Erweitern Sie die Lösung auf 2D (maximales Rechteck). Zeigen Sie, dass Sie das Problem auf n Histogrammprobleme reduzieren können, jeweils mit O(n), also insgesamt O(m×n).
# Final clean solution for interview
def largest_rectangle_in_histogram(heights):
stack = []
max_area = 0
for i, h in enumerate(heights + [0]): # sentinel forces final pops
while stack and heights[stack[-1]] > h:
height = heights[stack.pop()]
width = i if not stack else i - stack[-1] - 1
max_area = max(max_area, height * width)
stack.append(i)
return max_area
# Verify all test cases from earlier
test_cases = [
([2, 1, 5, 6, 2, 3], 10),
([6, 7, 5, 2, 4, 5, 9, 3], 16),
([1], 1),
([2, 0, 2], 2),
([], 0),
]
for heights, expected in test_cases:
if not heights:
result = 0
else:
result = largest_rectangle_in_histogram(heights)
status = 'PASS' if result == expected else 'FAIL'
print(f'{status}: {heights} => {result} (expected {expected})')Summe der Spannweiten von Teilarrays und ähnliche Varianten
Die PSE/NSE-Technik lässt sich auf mehrere LeetCode-Probleme verallgemeinern. Summe der Spannweiten von Teilarrays (2104) verlangt die Summe von (Maximum - Minimum) über alle Teilarrays. Das entspricht der (Summe der Teilarray-Maxima) minus (Summe der Teilarray-Minima), wobei beides mit einem monotonen Stack in O(n) berechnet wird. Anzahl sichtbarer Personen in einer Warteschlange (1944) verwendet einen absteigenden Stack, bei dem jeder Pop eine sichtbare Person zählt. Sie erkennen diese Problemfamilie an der Formulierung „Wie weit kann jedes Element dominieren?“ – die Antwort lautet immer PSE/NSE mit einem monotonen Stack.
def sum_subarray_ranges(nums):
n = len(nums)
# Sum of subarray max - sum of subarray min
def contrib(arr, is_max):
# Count contribution of each element as max (or min)
n = len(arr)
left = [0]*n; right = [0]*n
stack = []
for i in range(n):
while stack and (arr[stack[-1]] < arr[i] if is_max else arr[stack[-1]] > arr[i]):
stack.pop()
left[i] = i - (stack[-1] if stack else -1)
stack.append(i)
stack = []
for i in range(n-1, -1, -1):
while stack and (arr[stack[-1]] <= arr[i] if is_max else arr[stack[-1]] >= arr[i]):
stack.pop()
right[i] = (stack[-1] if stack else n) - i
stack.append(i)
return sum(arr[i] * left[i] * right[i] for i in range(n))
return contrib(nums, True) - contrib(nums, False)
print(sum_subarray_ranges([1, 2, 3])) # 4
print(sum_subarray_ranges([1, 3, 3])) # 4
print(sum_subarray_ranges([4, -2, -3, 4, 1])) # 59Schnelltest
Testen Sie Ihr Verständnis der Konzepte aus Data Structures & Algorithms — Coding Interview Prep in dieser Lektion.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: Für jeden Balken werden die Grenzen des größten ihn enthaltenden Rechtecks durch den jeweils nächsten kürzeren Balken auf beiden Seiten definiert (PSE und NSE), ein monoton steigender Stack berechnet alle PSE/NSE-Grenzen in einem einzigen O(n)-Durchlauf, indem beide Grenzen beim Entfernen von Balken gefunden werden, und ein angehängter Sentinel 0 stellt sicher, dass alle Balken aus dem Stack entfernt werden, wodurch der Code auf eine einzige Schleife vereinfacht wird. Als Nächstes verwenden wir die monotone Deque, um das Maximum in einem Gleitfenster in O(n) zu bestimmen.
Häufig gestellte Fragen
Ist die Lektion „Größtes Rechteck im Histogramm“ kostenlos?
Ja — der vollständige Text von „Größtes Rechteck im Histogramm“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Coding Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Größtes Rechteck im Histogramm“?
Verwenden Sie einen monotonen Stack, um linke Grenzen zu verfolgen und die maximale Rechtecksfläche innerhalb eines Histogramms in einem einzigen Durchlauf zu berechnen. Du übst Coding Interview Prep mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Coding Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. Coding Interview Prep auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.
Wie lange dauert die Lektion „Größtes Rechteck im Histogramm“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Coding Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede Coding Interview Prep-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Monotoner Stack: aufsteigend vs. absteigend
- Größtes Rechteck im Histogramm
- Sliding-Window-Maximum mit monotoner Deque
- Trapping Rain Water: Stack und Zwei-Zeiger