Persegi Panjang Terbesar dalam Histogram
Gunakan tumpukan monotonik untuk melacak batas kiri dan menghitung luas maksimum persegi panjang yang dapat dimuat dalam histogram dalam satu lintasan
Persegi Panjang Terbesar dalam Histogram adalah pelajaran Coding Interview Prep gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Coding Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Masalah: Persegi Panjang Terbesar dalam Histogram
Masalah Persegi Panjang Terbesar dalam Histogram (LeetCode 84) memberikan larik bilangan bulat nonnegatif yang merepresentasikan tinggi batang dalam histogram, dengan setiap batang memiliki lebar 1. Temukan luas persegi panjang terbesar yang dapat dibentuk di dalam histogram. Persegi panjang tersebut harus mencakup batang-batang yang bersebelahan, dan tingginya dibatasi oleh batang terpendek yang dicakupnya.
Pendekatan dengan mencoba semua kemungkinan: untuk setiap pasangan (i, j), hitung tinggi minimum dalam [i, j], lalu kalikan dengan (j - i + 1). Ini menghasilkan O(n³), atau O(n²) dengan nilai minimum yang telah dihitung sebelumnya — terlalu lambat. Solusi tumpukan monoton berjalan dalam 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)) # 10Wawasan Utama: Apa yang Membatasi Persegi Panjang Setiap Batang?
Untuk setiap batang i dengan tinggi h, persegi panjang terbesar yang dapat menjadikan batang tersebut sebagai minimum memanjang ke kiri hingga batang pertama yang lebih pendek daripada h, dan ke kanan hingga batang pertama yang lebih pendek daripada h. Lebarnya adalah right_boundary - left_boundary - 1 dan luasnya adalah h × width.
Ini mengubah cara pandang terhadap masalah: untuk setiap batang, temukan elemen lebih kecil sebelumnya (PSE) dan elemen lebih kecil berikutnya (NSE). Keduanya persis seperti yang dihitung oleh tumpukan meningkat monoton. Saat kita melakukan pop pada batang i (karena batang yang lebih pendek ditemukan), batang saat ini adalah NSE-nya dan puncak tumpukan setelah pop adalah PSE-nya.
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)Solusi Satu Lintasan dengan Tumpukan Monoton
Pendekatan dua lintasan di atas dapat digunakan, tetapi juga dapat digabungkan menjadi satu lintasan. Proses batang dari kiri ke kanan dengan tumpukan meningkat monoton. Ketika batang i lebih pendek daripada puncak tumpukan, lakukan pop pada puncak tumpukan — tinggi batang yang dikeluarkan adalah tinggi sebuah persegi panjang, batas kanannya adalah i, dan batas kirinya adalah puncak tumpukan baru + 1.
Trik yang umum digunakan: append penanda 0 di akhir heights. Ini memastikan semua batang dikeluarkan dari tumpukan pada akhir, bahkan jika tidak ada batang yang lebih pendek secara alami. Tanpa penanda tersebut, Anda perlu melakukan pembersihan setelah perulangan untuk elemen tumpukan yang tersisa.
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])) # 16Menelusuri Algoritme Satu Lintasan
Mari kita menelusuri [2, 1, 5, 6, 2, 3, 0] (dengan penanda) langkah demi langkah:
- i=0, h=2: masukkan 0. Tumpukan: [0]
- i=1, h=1: lakukan pop pada 0 (h=2, lebar=1, luas=2). Tumpukan kosong, masukkan 1. Tumpukan: [1]
- i=2, h=5: 5>1, masukkan 2. Tumpukan: [1,2]
- i=3, h=6: 6>5, masukkan 3. Tumpukan: [1,2,3]
- i=4, h=2: lakukan pop pada 3 (h=6,lebar=4-2-1=1,luas=6), lakukan pop pada 2 (h=5,lebar=4-1-1=2,luas=10★), 2>1, berhenti. Masukkan 4. Tumpukan: [1,4]
- i=5, h=3: 3>2, masukkan 5. Tumpukan: [1,4,5]
- i=6, penanda h=0: lakukan pop pada semuanya, sambil menghitung luas...
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])Perhitungan Lebar: Mengapa i - stack[-1] - 1?
Ketika kita melakukan pop pada batang j dari tumpukan, kita mengetahui bahwa: batas kanan persegi panjang batang j adalah i (batang pertama di sebelah kanan yang lebih pendek daripada j). Batas kirinya adalah batang yang tepat berada di bawah j dalam tumpukan setelah pop — sebut saja k. Oleh karena itu, lebarnya adalah i - k - 1 (batang dari k+1 hingga i-1 secara inklusif).
Jika tumpukan kosong setelah pop, persegi panjang j memanjang hingga tepi kiri (indeks 0). Lebarnya cukup i (indeks 0 hingga i-1, yang semuanya setidaknya setinggi heights[j]). Ini adalah kasus khusus 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])Persegi Panjang Maksimal dalam Matriks Biner
Persegi Panjang Maksimal (LeetCode 85) memperluas masalah histogram ke matriks biner 2D. Untuk setiap baris, hitung tinggi angka 1 berturut-turut di atas setiap sel. Dengan demikian, terbentuk histogram untuk baris tersebut. Terapkan algoritme persegi panjang terbesar dalam histogram pada histogram setiap baris. Nilai maksimum keseluruhan dari semua baris adalah jawabannya.
Ini mengubah masalah 2D menjadi n masalah histogram 1D yang diulang. Kompleksitas waktunya adalah O(m × n) untuk matriks dengan m baris dan n kolom — satu lintasan histogram untuk setiap baris, dengan setiap lintasan berkompleksitas O(n).
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)) # 6Kasus Tepi dalam Masalah Histogram
Kasus tepi penting yang perlu ditangani:
- Semua tinggi sama: seluruh larik membentuk satu persegi panjang; jawaban = n × tinggi
- Meningkat secara monoton: tidak ada pop hingga penanda; luas batang terakhir adalah yang terbesar
- Satu batang: jawaban = tinggi[0]
- Batang dengan tinggi 0: batang tersebut berfungsi sebagai penanda alami yang membagi histogram menjadi segmen-segmen terpisah
Penanda (append 0) di akhir menangani kasus yang meningkat secara monoton dengan memaksa semua batang yang tersisa dikeluarkan di akhir. Tanpa penanda tersebut, Anda memerlukan perulangan pembersihan terpisah setelah iterasi utama.
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)Alternatif Bagi dan Taklukkan
Masalah histogram juga dapat diselesaikan dengan pendekatan bagi dan taklukkan: bagi pada batang dengan tinggi minimum, selesaikan setiap bagian secara rekursif, lalu bandingkan dengan persegi panjang yang mencakup seluruh lebar menggunakan tinggi minimum. Pendekatan ini menghasilkan O(n log n) secara rata-rata, tetapi O(n²) pada kasus terburuk untuk masukan yang terurut.
Pendekatan tumpukan monoton jelas lebih baik, yaitu O(n) pada kasus terburuk. Namun, memahami pendekatan bagi dan taklukkan memperdalam intuisi terhadap masalah dan menjelaskan mengapa batang dengan tinggi minimum dalam segmen mana pun selalu menjadi faktor pembatas untuk persegi panjang yang mencakup seluruh lebar.
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])) # 10Pola Histogram: Jumlah Sublarik
Masalah terkait yang menggunakan teknik tumpukan yang sama: hitung jumlah sublarik dalam histogram yang elemen minimumnya sama dengan target tertentu. Hal ini dijawab dengan menghitung PSE dan NSE untuk setiap batang, kemudian menggunakan rumus (i - pse[i]) × (nse[i] - i) yang menghitung subhistogram ketika batang i merupakan elemen minimum.
Teknik “jumlah kiri × jumlah kanan” ini muncul dalam beberapa masalah LeetCode: jumlah elemen minimum sublarik (907), jumlah substring dengan semua karakter unik, dan masalah teknik kontribusi. Tumpukan monoton menghitung PSE dan NSE dalam O(n), sehingga kontribusi setiap elemen dapat dihitung dalam 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])) # 444Kiat Praktis untuk Wawancara
Saat menemui soal histogram dalam wawancara, ikuti daftar periksa ini:
- Perjelas: apakah tinggi boleh bernilai 0? Apa keluarannya—luas, indeks, atau jumlah?
- Mulailah dengan solusi coba semua kemungkinan dan nyatakan kompleksitas O(n²) atau O(n³)
- Sebutkan bahwa kontribusi setiap batang bergantung pada jangkauan kiri dan kanannya hingga batang terdekat yang lebih pendek
- Perkenalkan PSE/NSE → tumpukan monoton → solusi O(n)
- Tangani trik penanda akhir (append 0) untuk menyederhanakan kode
- Telusuri contoh kecil di papan tulis
Pertanyaan lanjutan yang umum: perluas ke 2D (persegi panjang terbesar). Tunjukkan bahwa Anda dapat mereduksinya menjadi n soal histogram, masing-masing O(n), dengan total 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})')Jumlah Rentang Sublarik dan Variasi Serupa
Teknik PSE/NSE berlaku umum untuk beberapa soal LeetCode. Jumlah Rentang Sublarik (2104) meminta jumlah (maksimum - minimum) di seluruh sublarik. Ini sama dengan (jumlah maksimum sublarik) dikurangi (jumlah minimum sublarik), yang masing-masing dihitung dengan tumpukan monoton dalam O(n). Jumlah Orang yang Terlihat dalam Antrean (1944) menggunakan tumpukan menurun, dengan setiap pop menghitung satu orang yang terlihat. Mengenali keluarga soal ini berasal dari pengamatan terhadap frasa 'untuk setiap elemen, seberapa jauh elemen tersebut dapat mendominasi?'—jawabannya selalu PSE/NSE dengan tumpukan monoton.
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])) # 59Uji Cepat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari: untuk setiap batang, persegi panjang terbesar yang memuatnya memiliki batas yang ditentukan oleh batang terdekat yang lebih pendek di setiap sisi (PSE dan NSE), tumpukan monoton menaik menghitung semua batas PSE/NSE dalam satu lintasan O(n) dengan menemukan keduanya saat batang dikeluarkan, dan menambahkan penanda akhir 0 memastikan semua batang dikeluarkan dari tumpukan, sehingga kode dapat disederhanakan menjadi satu perulangan. Berikutnya kita menerapkan antrean dua ujung monoton untuk menyelesaikan maksimum jendela geser dalam O(n).
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Persegi Panjang Terbesar dalam Histogram” gratis?
Ya — teks lengkap “Persegi Panjang Terbesar dalam Histogram” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Coding Interview Prep, upgrade ke CoddyKit PRO. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Persegi Panjang Terbesar dalam Histogram”?
Gunakan tumpukan monotonik untuk melacak batas kiri dan menghitung luas maksimum persegi panjang yang dapat dimuat dalam histogram dalam satu lintasan Kamu berlatih Coding Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai Coding Interview Prep?
Tidak diperlukan pengalaman sebelumnya. Coding Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.
Berapa lama pelajaran “Persegi Panjang Terbesar dalam Histogram” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran Coding Interview Prep ini?
Ya. Setiap pelajaran Coding Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Tumpukan Monotonik: Menaik vs Menurun
- Persegi Panjang Terbesar dalam Histogram
- Maksimum Jendela Geser dengan Deque Monotonik
- Menampung Air Hujan: Tumpukan dan Dua Penunjuk