Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi
Yatay katmanları hesaplayan tekdüze yığın yaklaşımını ve dikey sütunları hesaplayan iki işaretçi yaklaşımını kullanarak yağmur suyu biriktirme problemini çözün.
Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi, CoddyKit'te ücretsiz bir Coding Interview Prep dersidir. Bu, 4 dersinin 4. 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, Coding Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Coding Interview Prep kursu toplamda 4 dersten oluşur.
Problem: Yağmur Suyu Tutma
Yağmur Suyu Tutma (LeetCode 42), en ikonik mülakat problemlerinden biridir. Her çubuğun genişliği 1 olan bir yükseklik haritasını temsil eden n negatif olmayan tamsayı verildiğinde, yağmurdan sonra çubuklar arasında ne kadar su tutulabileceğini hesaplayın. Su, her iki tarafında daha yüksek çubuklar bulunan çukurlarda birikir.
Her i konumu için su seviyesi min(max_left[i], max_right[i]) - height[i] şeklindedir. Bu değer negatifse su tutulmaz (çubuk en az bir sınırdan daha yüksektir). Üç yaklaşım vardır: önceden hesaplanmış diziler O(n)/O(n), iki işaretçi O(n)/O(1) ve monotonik yığın O(n)/O(n).
height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
# Water trapped at each position:
# pos 2: min(1,3)-0=1
# pos 4: min(2,3)-1=1
# pos 5: min(2,3)-0=2
# pos 6: min(2,3)-1=1
# pos 9: min(3,2)-1=1
# Total = 6
print('height:', height)
print('Expected trapped water: 6')
# Visualise
max_h = max(height)
for row in range(max_h, 0, -1):
line = ''
for h in height:
line += '#' if h >= row else ' '
print(line)Yaklaşım 1: Önceden Hesaplanmış Maksimum Dizileri
Doğrudan O(n) zamanlı, O(n) alanlı çözüm iki diziyi önceden hesaplar: max_left[i] = 0 indisinden i indisine kadar olan maksimum yükseklik ve max_right[i] = i indisinden n-1 indisine kadar olan maksimum yükseklik. i konumundaki su miktarı max(0, min(max_left[i], max_right[i]) - height[i]) şeklindedir.
max_left oluşturmak için soldan sağa tek bir geçiş, max_right oluşturmak için sağdan sola bir geçiş gerekir. Son bir geçiş suyu toplar. Bu yaklaşım temiz ve açıklaması kolaydır; ancak O(n) ek alan kullanır.
def trap_prefix(height):
n = len(height)
if n < 3:
return 0
max_left = [0] * n
max_right = [0] * n
max_left[0] = height[0]
for i in range(1, n):
max_left[i] = max(max_left[i-1], height[i])
max_right[-1] = height[-1]
for i in range(n-2, -1, -1):
max_right[i] = max(max_right[i+1], height[i])
water = 0
for i in range(n):
water += max(0, min(max_left[i], max_right[i]) - height[i])
return water
print(trap_prefix([0,1,0,2,1,0,1,3,2,1,2,1])) # 6
print(trap_prefix([4,2,0,3,2,5])) # 9Yaklaşım 2: İki İşaretçi (O(1) Alan)
İki işaretçi yaklaşımı O(n) zamanda ve O(1) alanda çalışır. İki uçtan başlayan sol ve sağ işaretçileri kullanın. Her iki taraftan şimdiye kadar görülen çalışan maksimumlar olarak max_left ve max_right değerlerini koruyun.
Her adımda, çalışan maksimumu daha küçük olan tarafı işleyin — çünkü sınırlayıcı etken odur. max_left < max_right ise sol işaretçinin bulunduğu konumdaki su max_left - height[left] olur (sağ taraf yeterince yüksektir). Sol işaretçiyi içeri doğru ilerletin. Aksi durumda sağ tarafı simetrik biçimde işleyin. Önceden hesaplanmış dizilere gerek yoktur.
def trap_two_pointer(height):
left, right = 0, len(height) - 1
max_left = max_right = 0
water = 0
while left < right:
if height[left] < height[right]:
if height[left] >= max_left:
max_left = height[left] # new max on the left
else:
water += max_left - height[left] # trapped by max_left
left += 1
else:
if height[right] >= max_right:
max_right = height[right]
else:
water += max_right - height[right]
right -= 1
return water
print(trap_two_pointer([0,1,0,2,1,0,1,3,2,1,2,1])) # 6
print(trap_two_pointer([4,2,0,3,2,5])) # 9
print(trap_two_pointer([3,0,3])) # 3İki İşaretçi Neden Çalışır: Değişmez Koşul
Temel kavrayış şudur: height[left] < height[right] olduğu için sol işaretçiyi işlediğimizde max_right >= height[right] > height[left] olduğunu biliriz. Dolayısıyla sağdaki etkin su sınırı en az height[right] kadardır ve bu değer zaten max_left'ten büyüktür. Böylece min(max_left, effective_max_right) = max_left olur ve su formülü max_left - height[left] biçiminde sadeleşir.
Kesin max_right değerini bilmemize gerek yoktur — bunun en az height[right] > height[left] kadar olduğunu bilmek, su seviyesi olarak max_left değerini kullanmak için yeterlidir. O(1) alanı mümkün kılan zarif değişmez koşul budur.
# Trace two-pointer on [4, 2, 0, 3, 2, 5]
height = [4, 2, 0, 3, 2, 5]
left, right = 0, len(height) - 1
max_l = max_r = water = 0
print('height:', height)
print(f'{'Step':5} {'L':3} {'R':3} {'maxL':5} {'maxR':5} {'water':6} {'total':6}')
step = 0
while left < right:
side = 'L' if height[left] < height[right] else 'R'
if side == 'L':
if height[left] >= max_l: max_l = height[left]
else:
w = max_l - height[left]; water += w
left += 1
else:
if height[right] >= max_r: max_r = height[right]
else:
w = max_r - height[right]; water += w
right -= 1
step += 1
print(f'{step:5} {left:3} {right:3} {max_l:5} {max_r:5} {water:6}')
print('Total trapped:', water)3. Yaklaşım: Monoton Yığın (Yatay Katmanlar)
Monoton yığın yaklaşımı, bitişik çubuklar arasındaki suyu yatay katmanlar olarak hesaplar. Azalan monoton bir indis yığını tutun. i çubuğu, yığının tepesindeki j çubuğundan daha yüksek olduğunda bir çukur oluşur: taban height[j], j çıkarıldıktan sonraki sol duvar height[stack[-1]] ve sağ duvar height[i] olur. Su, min(left_wall, right_wall) - floor seviyesine kadar çukuru doldurur; genişlik ise i - stack[-1] - 1 olur.
Her 'çukur', daha yüksek bir çubukla karşılaşıldığında hesaplanır. Bu yöntem suyu sınırlı dikdörtgen bölümler hâlinde işler; bu da su seviyesine hangi çubukların katkıda bulunduğunu izlemeniz gerektiğinde yararlıdır.
def trap_stack(height):
stack = [] # monotonic decreasing indices
water = 0
for i in range(len(height)):
while stack and height[stack[-1]] < height[i]:
bottom_idx = stack.pop() # the floor of the valley
if not stack:
break # no left wall, no water
left_idx = stack[-1]
floor = height[bottom_idx]
water_height = min(height[left_idx], height[i]) - floor
width = i - left_idx - 1
water += water_height * width
stack.append(i)
return water
print(trap_stack([0,1,0,2,1,0,1,3,2,1,2,1])) # 6
print(trap_stack([4,2,0,3,2,5])) # 9Monoton Yığını İzleme
Yığın yaklaşımıyla [0,1,0,2,1,0,1,3,...] dizisini izleyelim. i=3 konumunda çubuk 3'e (h=2) ulaştığımızda yığının tepesinde i=2 (h=0) vardır; bu çubuğu çıkarırız. Sol duvar i=1 (h=1), sağ duvar ise h=2 olur. Su yüksekliği = min(1,2)-0=1, genişlik=3-1-1=1 ve alan=1 olur. Devam edelim: yığının tepesindeki i=1 (h=1), 2'den küçük değildir; dururuz. Ardından 3'ü yığına ekleriz.
Yığın yönteminin uygulanması iki işaretçili yönteme göre daha karmaşıktır; ancak her su hücresini hangi çubukların oluşturduğunu ortaya çıkarır. Bu içgörü, su düzenini yeniden oluşturma veya farklı çukurları sayma hakkındaki takip sorularında yararlıdır.
def trap_stack_trace(height):
stack = []
water = 0
for i in range(len(height)):
print(f'i={i} h={height[i]}: stack={[height[s] for s in stack]}')
while stack and height[stack[-1]] < height[i]:
bot = stack.pop()
if not stack:
print(f' Pop {height[bot]}: no left wall, skip')
break
left = stack[-1]
h = min(height[left], height[i]) - height[bot]
w = i - left - 1
water += h * w
print(f' Pop {height[bot]}: floor={height[bot]}, left_wall={height[left]}, right_wall={height[i]}, h={h}, w={w}, +{h*w}')
stack.append(i)
return water
result = trap_stack_trace([0,1,0,2,1,0,1,3,2,1,2,1])
print('Total:', result)Üç Yaklaşımın Karşılaştırılması
Yağmur suyu biriktirme yaklaşımlarının özeti:
- Önek dizileri: O(n) zaman, O(n) alan. Anlaması ve doğrulaması en kolay yöntemdir. Alan verimliliğinden çok açıklığın önemsendiği mülakatlar için en uygunudur.
- İki işaretçi: O(n) zaman, O(1) alan. Hem zaman hem alan açısından en iyi yöntemdir. 'O(1) alan kullanabilir misiniz?' takip soruları için en uygunudur.
- Monoton yığın: O(n) zaman, O(n) alan. Suyu yatay katmanlar hâlinde işler. Hangi çubukların katkıda bulunduğunu bilmeniz gerektiğinde veya bu problem daha büyük, yığın tabanlı bir algoritmada alt problem olarak karşınıza çıktığında en uygunudur.
height = [0,1,0,2,1,0,1,3,2,1,2,1]
# All three methods — verify they agree
def trap_prefix(h):
n = len(h)
ml = [0]*n; mr = [0]*n; ml[0]=h[0]; mr[-1]=h[-1]
for i in range(1,n): ml[i]=max(ml[i-1],h[i])
for i in range(n-2,-1,-1): mr[i]=max(mr[i+1],h[i])
return sum(max(0,min(ml[i],mr[i])-h[i]) for i in range(n))
def trap_two_ptr(h):
l,r,ml,mr,w = 0,len(h)-1,0,0,0
while l<r:
if h[l]<h[r]:
ml=max(ml,h[l]); w+=ml-h[l]; l+=1
else:
mr=max(mr,h[r]); w+=mr-h[r]; r-=1
return w
def trap_stk(h):
stk,w = [],[]
for i in range(len(h)):
while stk and h[stk[-1]]<h[i]:
b=stk.pop()
if not stk: break
w.append(max(0,min(h[stk[-1]],h[i])-h[b])*(i-stk[-1]-1))
stk.append(i)
return sum(w)
for h in [height, [4,2,0,3,2,5], [3,0,3], [1,0,1]]:
p=trap_prefix(h); t=trap_two_ptr(h); s=trap_stk(h)
print(f'{h}: prefix={p}, two-ptr={t}, stack={s}, match={p==t==s}')En Çok Su Alan Kap
En Çok Su Alan Kap (LeetCode 11) problemi, yağmur suyu biriktirme problemiyle sıkça karıştırılır. Burada tam olarak iki çubuk seçersiniz ve su yalnızca bu iki çubukla sınırlandırılır; içteki çubukların hiçbir etkisi yoktur. min(height[l], height[r]) × (r - l) alanını en büyük hâle getirin.
İki işaretçi bu problemi açgözlü bir yöntemle çözer: en geniş aralıkla başlamak için iki uçtan başlayın. Daha kısa olan işaretçiyi içeri doğru ilerletin; daha uzun olanı ilerletmek alanı yalnızca küçültebilir. Bu yöntem O(n) zaman ve O(1) alan kullanır; güncel maksimum değer tutmak gerekmediğinden yağmur suyu biriktirmedeki iki işaretçili yöntemden daha basittir.
def max_water_container(height):
left, right = 0, len(height) - 1
max_area = 0
while left < right:
area = min(height[left], height[right]) * (right - left)
max_area = max(max_area, area)
# Move the shorter bar: moving taller bar can only reduce min
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area
print(max_water_container([1,8,6,2,5,4,8,3,7])) # 49: bars 8 and 7
print(max_water_container([1,1])) # 1
print(max_water_container([4,3,2,1,4])) # 16
# Key difference from trapping rain water:
# Container: choose 2 bars, water fills freely between them (no internal barriers)
# Trapping: water fills ALL valleys in the full elevation mapİleri Düzey: Trapping Rain Water II (3B)
Trapping Rain Water II (LeetCode 407), problemi 2B bir yükseklik matrisine genişletir. Su dört yöne de akabilir ve sınırların üzerinden dışarı çıkmalıdır. Çözümde bir minimum yığını kullanılır: önce tüm sınır hücrelerini yığına ekleyin, ardından BFS benzeri bir genişleme gerçekleştirin. Yüksekliği en küçük hücreyi işleyin; daha alçak olan her komşu, en azından mevcut hücrenin seviyesinde su tutmalıdır.
Bu, 1B durumdaki algoritmadan temelde farklı bir algoritmadır ve hem yığın işlemlerini hem de BFS dolaşımını sınar. 1B iki işaretçi hilesi 2B'ye genellenemez; yığın yaklaşımı genellenebilir.
import heapq
def trap_rain_water_2d(heightMap):
if not heightMap or not heightMap[0]:
return 0
m, n = len(heightMap), len(heightMap[0])
visited = [[False]*n for _ in range(m)]
heap = [] # (height, row, col)
# Add all border cells to the heap
for i in range(m):
for j in [0, n-1]:
heapq.heappush(heap, (heightMap[i][j], i, j))
visited[i][j] = True
for j in range(n):
for i in [0, m-1]:
if not visited[i][j]:
heapq.heappush(heap, (heightMap[i][j], i, j))
visited[i][j] = True
total = 0
max_h = 0
while heap:
h, r, c = heapq.heappop(heap)
max_h = max(max_h, h)
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
nr, nc = r+dr, c+dc
if 0<=nr<m and 0<=nc<n and not visited[nr][nc]:
visited[nr][nc] = True
total += max(0, max_h - heightMap[nr][nc])
heapq.heappush(heap, (max(max_h, heightMap[nr][nc]), nr, nc))
return total
map2d = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
print(trap_rain_water_2d(map2d)) # 4Mülakatlarda Her Yöntem Ne Zaman Kullanılmalı
Yağmur suyu biriktirme mülakatı için karar kılavuzu:
- Şununla başlayın: önek dizileri — açıklaması kolay, görsel olarak sezgisel ve doğruluğu açık
- 'O(1) alan mı?' takip sorusu: iki işaretçi — daha küçük tarafın darboğaz olduğunu belirten değişmezi açıklayın
- Mülakatçı 'başka bir yaklaşım?' diye sorarsa: monoton yığın — yatay katman hesaplamasını açıklayın
Koda geçmeden önce her konumdaki su seviyesini neyin belirlediğini (her iki taraftaki en yüksek çubukların minimumunu) her zaman açıkça tanımlayın. Bu, problemi anladığınızı gösterir ve çözümü açıklamayı kolaylaştırır.
# Quick summary of all three approaches
approaches = [
{
'name': 'Prefix max arrays',
'time': 'O(n)', 'space': 'O(n)',
'description': '3 passes: build max_left, max_right, sum water column-by-column',
},
{
'name': 'Two pointers',
'time': 'O(n)', 'space': 'O(1)',
'description': 'Process smaller side: its max is the limiting wall, no array needed',
},
{
'name': 'Monotonic stack',
'time': 'O(n)', 'space': 'O(n)',
'description': 'Compute water in horizontal layers when a taller bar is encountered',
},
]
for a in approaches:
print(f'{a["name"]} [{a["time"]} / {a["space"]}]')
print(f' {a["description"]}')
print()Sınır Durumları ve Yaygın Hatalar
Yağmur suyu biriktirme problemindeki yaygın hatalar:
- Minimumu unutmak: su seviyesi yalnızca birinin değil,
min(max_left, max_right)değerinin minimumudur. Bir çubuğun her iki tarafında da yüksek duvarlar bulunmalıdır. - Negatif su: bir konumdaki yükseklik su seviyesini aştığında negatif değerleri 0'a sınırlamak için
max(0, ...)kullanın. - Kenar konumları: en soldaki ve en sağdaki çubuklar hiçbir zaman su tutamaz; çünkü bir taraflarında duvar yoktur. Önek dizisi yaklaşımı bunu doğal olarak ele alır;
max_left[0] = height[0]olduğundan 0 indisindeki su her zaman 0 olur. - Boş veya çok küçük diziler: 3'ten az öğe içeren diziler için 0 döndürün.
def trap(height):
n = len(height)
if n < 3:
return 0 # need at least 3 bars to trap anything
left, right = 0, n - 1
max_l = max_r = water = 0
while left < right:
if height[left] <= height[right]:
if height[left] >= max_l:
max_l = height[left]
else:
water += max_l - height[left] # never negative: max_l > height[left]
left += 1
else:
if height[right] >= max_r:
max_r = height[right]
else:
water += max_r - height[right]
right -= 1
return water
# Edge cases
print(trap([])) # 0: empty
print(trap([1])) # 0: single bar
print(trap([1,2])) # 0: two bars
print(trap([3,0,3])) # 3: simple valley
print(trap([3,3,3])) # 0: flat top, no waterHızlı Kontrol
Bu dersteki Veri Yapıları ve Algoritmalar — Kodlama Mülakatı Hazırlığı kavramlarını anlayışınızı sınayın.
Ders Özeti
Bu derste şunları öğrendiniz: yağmur suyu biriktirme, her konumda soldaki ve sağdaki en yüksek duvarların minimumunu bularak çözülür, O(1) alan kullanan iki işaretçili yaklaşım, daha küçük taraftaki güncel maksimumun her zaman belirleyici kısıt olmasına dayanır ve monoton yığın yaklaşımı suyu yatay katmanlar hâlinde hesaplar; bu yaklaşım, diğer yığın tabanlı mantıklarla birleştirildiğinde yararlıdır. Sırada, yapılandırılmış mülakat yanıtları için RADIO çerçevesiyle başlayarak sistem tasarımı kavramlarına geçeceğiz.
Sıkça Sorulan Sorular
“Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi” dersi ücretsiz mi?
Evet — “Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi” 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 Coding Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Coding Interview Prep kursu toplamda 4 dersten oluşur.
“Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi” dersinde ne öğreneceğim?
Yatay katmanları hesaplayan tekdüze yığın yaklaşımını ve dikey sütunları hesaplayan iki işaretçi yaklaşımını kullanarak yağmur suyu biriktirme problemini çözün. Coding 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.
Coding Interview Prep öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te Coding 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 4. dersidir.
“Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi” 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 Coding Interview Prep dersinde kod yazıp çalıştırabilir miyim?
Evet. Her Coding 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
- Tekdüze Yığın: Artan ve Azalan
- Histogramdaki En Büyük Dikdörtgen
- Tekdüze Kuyrukla Kayan Pencere Maksimumu
- Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi