0Pricing
DSA Interview Prep · Ders

Histogramdaki En Büyük Dikdörtgen

Sol sınırları izlemek ve histogram içine sığan en büyük alanlı dikdörtgeni tek geçişte hesaplamak için tekdüze bir yığın kullanın.

Histogramdaki En Büyük Dikdörtgen, CoddyKit'te ücretsiz bir DSA Interview Prep dersidir. Bu, 4 dersinin 2. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, DSA Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. DSA Interview Prep kursu toplamda 4 dersten oluşur.

Problem: Histogramda En Büyük Dikdörtgen

Histogramda En Büyük Dikdörtgen problemi (LeetCode 84), her çubuğun genişliğinin 1 olduğu bir histogramdaki çubuk yüksekliklerini temsil eden negatif olmayan tamsayılardan oluşan bir dizi verir. Histogram içinde oluşturulabilecek en büyük dikdörtgenin alanını bulun. Dikdörtgen bitişik çubukları kapsamalı ve yüksekliği, kapsadığı en kısa çubukla sınırlı olmalıdır.

Kaba kuvvet yaklaşımı: her (i, j) çifti için [i, j] aralığındaki minimum yüksekliği hesaplayıp (j - i + 1) ile çarpın. Bu yaklaşım, önceden hesaplanmış minimumlarla O(n²) veya O(n³) sürer — çok yavaştır. Monotonik yığın çözümü O(n) sürede çalışır.

# 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))  # 10

Temel İçgörü: Her Çubuğun Dikdörtgenini Ne Sınırlar?

Yüksekliği h olan her i çubuğu için, minimumu kendisi olabileceği en büyük dikdörtgen, h'den kısa ilk çubuğa ulaşıncaya kadar sola ve yine h'den kısa ilk çubuğa ulaşıncaya kadar sağa uzanır. Genişlik right_boundary - left_boundary - 1, alan ise h × width olur.

Bu bakış açısı problemi yeniden çerçeveler: her çubuk için önceki daha küçük elemanı (PSE) ve sonraki daha küçük elemanı (NSE) bulun. Bunlar, monotonik artan bir yığının tam olarak hesapladığı değerlerdir. Daha kısa bir çubuk bulunduğu için i çubuğunu pop ettiğimiz anda, mevcut çubuk onun NSE'si, pop işleminden sonraki yığın tepesi ise PSE'sidir.

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)

Monotonik Yığınla Tek Geçişli Çözüm

Yukarıdaki iki geçişli yaklaşım işe yarar; ancak tek geçişte birleştirilebilir. Çubukları soldan sağa, monotonik artan bir yığınla işleyin. i çubuğu yığın tepesinden kısa olduğunda yığın tepesini pop edin — pop edilen çubuğun yüksekliği bir dikdörtgenin yüksekliği, sağ sınırı i ve sol sınırı yığının yeni tepesi + 1 olur.

Yaygın bir yöntem: yükseklikler dizisinin sonuna bir nöbetçi 0 append edin. Böylece doğal olarak daha kısa bir çubuk görünmese bile tüm çubuklar sonunda yığından pop edilir. Nöbetçi değer olmadan, kalan yığın elemanları için döngü sonrasında ayrı bir temizleme aşaması gerekir.

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]))  # 16

Tek Geçişli Algoritmanın İzlenmesi

Nöbetçi değer eklenmiş [2, 1, 5, 6, 2, 3, 0] dizisini adım adım izleyelim:

  • i=0, h=2: 0'ı ekleyin. Yığın: [0]
  • i=1, h=1: 0'ı pop edin (h=2, width=1, area=2). Yığın boş, 1'i ekleyin. Yığın: [1]
  • i=2, h=5: 5>1, 2'yi ekleyin. Yığın: [1,2]
  • i=3, h=6: 6>5, 3'ü ekleyin. Yığın: [1,2,3]
  • i=4, h=2: 3'ü pop edin (h=6,width=4-2-1=1,area=6), 2'yi pop edin (h=5,width=4-1-1=2,area=10★), 2>1, durun. 4'ü ekleyin. Yığın: [1,4]
  • i=5, h=3: 3>2, 5'i ekleyin. Yığın: [1,4,5]
  • i=6, nöbetçi h=0: hepsini pop edin ve alanları hesaplayın...
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])

Genişlik Hesabı: Neden i - stack[-1] - 1?

j çubuğunu yığından pop ettiğimizde şunları biliriz: j'nin dikdörtgeninin sağ sınırı i'dir (j'den sağdaki ilk kısa çubuk). Sol sınır, pop işleminden sonra yığında j'nin hemen altında bulunan çubuktur; buna k diyelim. Bu nedenle genişlik i - k - 1 olur (k+1 ile i-1 arasındaki çubuklar, her iki sınır da dahil).

Pop işleminden sonra yığın boşsa j'nin dikdörtgeni sol kenara kadar uzanır (dizin 0). Genişlik basitçe i olur (0 ile i-1 arasındaki tüm dizinler; bunların tamamı heights[j] kadar veya daha yüksektir). Bu özel durum width = i if not stack else i - stack[-1] - 1 şeklindedir.

# 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])

İkili Matriste Maksimum Dikdörtgen

Maksimum Dikdörtgen (LeetCode 85), histogram problemini iki boyutlu bir ikili matrise genişletir. Her satır için, her hücrenin üstündeki ardışık 1'lerin yüksekliğini hesaplayın. Bu işlem, o satır için bir histogram oluşturur. Histogramda en büyük dikdörtgen algoritmasını her satırın histogramına uygulayın. Tüm satırlardaki en büyük değer genel yanıttır.

Bu yöntem, iki boyutlu bir problemi n kez tekrarlanan tek boyutlu histogram problemine indirger. m satırlı ve n sütunlu bir matris için zaman karmaşıklığı O(m × n)'dir; her satırda bir histogram geçişi yapılır ve her geçiş O(n) sürer.

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))  # 6

Histogram Problemlerinde Sınır Durumları

Ele alınması gereken önemli sınır durumları:

  • Tüm yükseklikler aynı: dizinin tamamı tek bir dikdörtgen oluşturur; yanıt = n × height
  • Monotonik olarak artan: nöbetçi değere kadar hiç pop işlemi gerçekleşmez; son çubuğun alanı en büyüktür
  • Tek çubuk: yanıt = height[0]
  • Yüksekliği 0 olan çubuklar: doğal nöbetçi değer görevi görerek histogramı bağımsız parçalara ayırır

Sondaki nöbetçi değer (0 eklenmesi), kalan tüm çubukları sonunda pop etmeye zorlayarak monotonik artan durumu ele alır. Bu değer olmadan, ana yinelemeden sonra ayrı bir temizleme döngüsü gerekir.

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)

Böl ve Fethet Alternatifi

Histogram problemi böl ve fethet yöntemiyle de çözülebilir: minimum yüksekliğe sahip çubuğun bulunduğu yerden bölün, her iki yarıyı yinelemeli olarak çözün ve minimum yüksekliği kullanarak tüm genişliği kapsayan dikdörtgenle karşılaştırın. Bu yöntem ortalama O(n log n), sıralanmış girdilerde ise en kötü durumda O(n²) süre verir.

Monotonik yığın yaklaşımı, en kötü durumda O(n) ile kesinlikle daha iyidir. Bununla birlikte, böl ve fethet yaklaşımını anlamak problem sezgisini geliştirir ve herhangi bir parçadaki minimum yüksekliğe sahip çubuğun, tüm genişliği kapsayan dikdörtgenler için neden her zaman sınırlayıcı etken olduğunu açıklar.

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]))  # 10

Histogram Örüntüsü: Alt Dizilerin Sayısı

Aynı yığın tekniğini kullanan ilgili bir problem: minimum elemanı belirli bir hedefe eşit olan histogram alt dizilerinin sayısını bulun. Bunun için her çubuk adına PSE ve NSE hesaplanır, ardından (i - pse[i]) × (nse[i] - i) formülü kullanılır; bu formül, i çubuğunun minimum olduğu alt histogramların sayısını verir.

Bu 'sol sayısı × sağ sayısı' tekniği çeşitli LeetCode problemlerinde görülür: alt dizi minimumlarının toplamı (907), tüm karakterleri birbirinden farklı olan alt dizelerin sayısı ve katkı tekniğini kullanan problemler. Monotonik yığın, PSE ve NSE'yi O(n) sürede hesaplayarak eleman başına O(1) katkı hesabını mümkün kılar.

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]))  # 444

Pratik Mülakat İpuçları

Bir mülakatta histogram problemi gördüğünüzde şu kontrol listesini izleyin:

  1. Netleştirin: yükseklikler 0 olabilir mi? Çıktı alan mı, indisler mi, yoksa sayı mı?
  2. Kaba kuvvetle başlayın ve O(n²) veya O(n³) karmaşıklığını belirtin
  3. Her çubuğun katkısının, kendisinden daha kısa en yakın çubuğa kadar soldaki ve sağdaki uzanımına bağlı olduğunu belirtin
  4. PSE/NSE → monotonik yığın → O(n) çözümünü tanıtın
  5. Kodu basitleştirmek için bekçi tekniğini (append 0) ele alın
  6. Beyaz tahtada küçük bir örneği adım adım izleyin

Yaygın bir devam sorusu, problemi 2B'ye (en büyük dikdörtgene) genişletmektir. Bunu her biri O(n) olan n histogram problemine indirgeyebildiğinizi ve toplamda O(m×n) elde edildiğini gösterin.

# 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})')

Alt Dizi Aralıklarının Toplamı ve Benzer Çeşitler

PSE/NSE tekniği birkaç LeetCode problemine genellenebilir. Alt Dizi Aralıklarının Toplamı (2104), tüm alt dizilerdeki (maksimum - minimum) değerlerinin toplamını ister. Bu, alt dizi maksimumlarının toplamı eksi alt dizi minimumlarının toplamına eşittir ve bunların her biri monotonik yığınla O(n) içinde hesaplanır. Kuyrukta Görülebilen Kişi Sayısı (1944), her pop işleminin görülebilen bir kişiyi saydığı azalan bir yığın kullanır. Bu problem ailesini tanımak, “her öğe için, ne kadar uzağa hâkim olabilir?” ifadesini fark etmekten geçer — yanıt her zaman monotonik yığınla PSE/NSE'dir.

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]))  # 59

Hızlı Kontrol

Bu derste ele alınan Veri Yapıları & Algoritmalar — Kodlama Mülakatı Hazırlığı kavramlarını ne ölçüde anladığınızı sınayın.

Ders Özeti

Bu derste şunları öğrendiniz: her çubuk için, onu içeren en büyük dikdörtgenin sınırları her iki taraftaki en yakın kısa çubuk tarafından belirlenir (PSE ve NSE), artan monotonik yığın, çubuklar çıkarılırken her ikisini de bularak tek bir O(n) geçişte tüm PSE/NSE sınırlarını hesaplar ve bir bekçi 0 eklemek tüm çubukların yığından çıkarılmasını garanti ederek kodu tek bir döngüye indirger. Sırada, kayar pencere maksimumunu O(n) içinde çözmek için monotonik çift uçlu kuyruğu uygulayacağız.

Sıkça Sorulan Sorular

“Histogramdaki En Büyük Dikdörtgen” dersi ücretsiz mi?

Evet — “Histogramdaki En Büyük Dikdörtgen” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve DSA Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. DSA Interview Prep kursu toplamda 4 dersten oluşur.

“Histogramdaki En Büyük Dikdörtgen” dersinde ne öğreneceğim?

Sol sınırları izlemek ve histogram içine sığan en büyük alanlı dikdörtgeni tek geçişte hesaplamak için tekdüze bir yığın kullanın. DSA Interview Prep ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

DSA Interview Prep öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te DSA Interview Prep, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 2. dersidir.

“Histogramdaki En Büyük Dikdörtgen” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu DSA Interview Prep dersinde kod yazıp çalıştırabilir miyim?

Evet. Her DSA Interview Prep dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Tekdüze Yığın: Artan ve Azalan
  2. Histogramdaki En Büyük Dikdörtgen
  3. Tekdüze Kuyrukla Kayan Pencere Maksimumu
  4. Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi
← DSA Interview Prep Sayfasına Dön