Menampung Air Hujan: Tumpukan dan Dua Penunjuk
Selesaikan masalah trapping-rain-water menggunakan pendekatan tumpukan monotonik yang menghitung lapisan horizontal dan pendekatan dua penunjuk yang menghitung kolom vertikal
Menampung Air Hujan: Tumpukan dan Dua Penunjuk adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 4 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 DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Soal: Menampung Air Hujan
Menampung Air Hujan (LeetCode 42) adalah salah satu soal wawancara paling ikonis. Diberikan n bilangan bulat nonnegatif yang merepresentasikan peta ketinggian, dengan setiap batang memiliki lebar 1, hitung banyak air yang dapat ditampung di antara batang setelah hujan turun. Air mengisi lembah di antara batang yang lebih tinggi di kedua sisinya.
Untuk setiap posisi i, tinggi airnya adalah min(max_left[i], max_right[i]) - height[i]. Jika hasilnya negatif, tidak ada air yang tertampung (batang tersebut lebih tinggi daripada setidaknya salah satu batas). Tiga pendekatan tersedia: larik yang telah dihitung sebelumnya O(n)/O(n), dua penunjuk O(n)/O(1), dan tumpukan monoton 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)Pendekatan 1: Larik Maksimum yang Telah Dihitung Sebelumnya
Solusi langsung dengan waktu O(n) dan ruang O(n) menghitung sebelumnya dua larik: max_left[i] = tinggi maksimum dari indeks 0 hingga i, dan max_right[i] = tinggi maksimum dari indeks i hingga n-1. Air pada posisi i adalah max(0, min(max_left[i], max_right[i]) - height[i]).
Membangun max_left memerlukan satu lintasan dari kiri ke kanan; max_right memerlukan satu lintasan dari kanan ke kiri. Lintasan terakhir menjumlahkan air. Pendekatan ini rapi dan mudah dijelaskan, tetapi menggunakan ruang tambahan O(n).
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])) # 9Pendekatan 2: Dua Penunjuk (Ruang O(1))
Pendekatan dua penunjuk mencapai waktu O(n) dan ruang O(1). Gunakan penunjuk kiri dan kanan yang dimulai dari kedua ujung. Pertahankan max_left dan max_right sebagai nilai maksimum berjalan yang sejauh ini terlihat dari masing-masing sisi.
Pada setiap langkah, proses sisi dengan maksimum berjalan yang lebih kecil—karena sisi itu merupakan faktor pembatas. Jika max_left < max_right, air pada penunjuk kiri adalah max_left - height[left] (sisi kanan cukup tinggi). Geser penunjuk kiri ke dalam. Jika tidak, proses penunjuk kanan secara simetris. Tidak diperlukan larik yang telah dihitung sebelumnya.
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])) # 3Mengapa Dua Penunjuk Berhasil: Invariannya
Gagasan utamanya: saat kita memproses penunjuk kiri karena height[left] < height[right], kita tahu bahwa max_right >= height[right] > height[left]. Oleh karena itu, batas air efektif di kanan setidaknya sebesar height[right], yang sudah lebih besar daripada max_left. Jadi min(max_left, effective_max_right) = max_left, dan rumus air menyederhana menjadi max_left - height[left].
Kita tidak perlu mengetahui max_right yang tepat—cukup mengetahui bahwa nilainya setidaknya sebesar height[right] > height[left] untuk menggunakan max_left sebagai tinggi air. Invarian elegan inilah yang memungkinkan penggunaan ruang O(1).
# 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)Pendekatan 3: Tumpukan Monotonik (Lapisan Horizontal)
Pendekatan tumpukan monotonik menghitung air dalam lapisan horizontal di antara batang-batang yang berdekatan. Pertahankan tumpukan indeks yang menurun secara monotonik. Ketika batang i lebih tinggi daripada bagian teratas tumpukan j, terbentuk cekungan: dasarnya adalah height[j], dinding kirinya adalah height[stack[-1]] setelah j dikeluarkan, dan dinding kanannya adalah height[i]. Air mengisi cekungan hingga min(left_wall, right_wall) - floor, dengan lebar i - stack[-1] - 1.
Setiap “cekungan” dihitung ketika batang yang lebih tinggi ditemukan. Cara ini memproses air dalam segmen persegi panjang yang dibatasi, sehingga berguna ketika Anda juga perlu melacak batang mana yang berkontribusi terhadap ketinggian air.
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])) # 9Menelusuri Tumpukan Monotonik
Mari kita telusuri [0,1,0,2,1,0,1,3,...] dengan pendekatan tumpukan. Ketika menemukan batang 3 (h=2) pada i=3: bagian teratas tumpukan adalah i=2 (h=0), lalu pop elemen tersebut. Dinding kiri adalah i=1 (h=1), sedangkan dinding kanan adalah h=2. Tinggi air = min(1,2)-0=1, lebar=3-1-1=1, luas=1. Selanjutnya, bagian teratas tumpukan i=1 (h=1) tidak lebih kecil daripada 2, jadi berhenti. Masukkan 3 ke tumpukan.
Metode tumpukan lebih rumit untuk diterapkan daripada metode dua penunjuk, tetapi menunjukkan batang-batang tertentu yang membentuk setiap sel air. Wawasan ini berguna untuk pertanyaan lanjutan tentang merekonstruksi susunan air atau menghitung cekungan yang berbeda.
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)Membandingkan Ketiga Pendekatan
Ringkasan tiga pendekatan penampungan air hujan:
- Larik prefiks: waktu O(n), ruang O(n). Paling mudah dipahami dan diverifikasi. Cocok untuk wawancara yang mengutamakan kejelasan daripada efisiensi ruang.
- Dua penunjuk: waktu O(n), ruang O(1). Optimal dalam waktu dan ruang. Cocok untuk pertanyaan lanjutan “dapatkah Anda menggunakan ruang O(1)?”.
- Tumpukan monotonik: waktu O(n), ruang O(n). Memproses air dalam lapisan horizontal. Cocok ketika Anda perlu mengetahui batang mana yang berkontribusi atau ketika masalah ini muncul sebagai submasalah dalam algoritma berbasis tumpukan yang lebih besar.
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}')Wadah dengan Air Terbanyak
Wadah dengan Air Terbanyak (LeetCode 11) sering tertukar dengan penampungan air hujan. Di sini, Anda memilih tepat dua batang dan air hanya dibatasi oleh kedua batang tersebut (batang di antaranya tidak berpengaruh). Maksimalkan luas min(height[l], height[r]) × (r - l).
Dua penunjuk menyelesaikannya dengan pendekatan tamak: mulai dari kedua ujung (lebar maksimum). Gerakkan penunjuk yang lebih pendek ke dalam—menggerakkan penunjuk yang lebih tinggi hanya dapat mengurangi luas. Cara ini membutuhkan waktu O(n) dan ruang O(1), serta lebih sederhana daripada metode dua penunjuk untuk penampungan air hujan karena tidak memerlukan nilai maksimum berjalan.
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 mapLanjutan: Penampungan Air Hujan II (3D)
Penampungan Air Hujan II (LeetCode 407) memperluas masalah ini menjadi matriks tinggi 2D. Air dapat mengalir ke keempat arah dan harus keluar melalui tepi. Solusinya menggunakan tumpukan minimum: inisialisasi tumpukan dengan semua sel tepi, lalu lakukan perluasan mirip BFS. Proses sel dengan tinggi terkecil—setiap tetangga yang lebih rendah harus menampung air setidaknya setinggi level sel saat ini.
Ini adalah algoritma yang secara mendasar berbeda dari kasus 1D dan menguji operasi tumpukan serta penelusuran BFS. Trik dua penunjuk untuk kasus 1D tidak dapat digeneralisasi ke 2D, sedangkan pendekatan tumpukan dapat.
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)) # 4Kapan Menggunakan Setiap Metode dalam Wawancara
Panduan pengambilan keputusan untuk wawancara penampungan air hujan:
- Mulai dengan: larik prefiks—mudah dijelaskan, intuitif secara visual, dan jelas kebenarannya
- Pertanyaan lanjutan “ruang O(1)?”: dua penunjuk—jelaskan invarian bahwa sisi yang lebih rendah adalah titik hambatnya
- Jika pewawancara bertanya “pendekatan lain?”: tumpukan monotonik—jelaskan perhitungan lapisan horizontal
Selalu mulai dengan mendefinisikan secara jelas hal yang menentukan ketinggian air di setiap posisi (nilai minimum dari batang tertinggi di setiap sisi) sebelum beralih ke kode. Ini menunjukkan pemahaman terhadap masalah dan membuat solusi lebih mudah dijelaskan.
# 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()Kasus Tepi dan Kesalahan Umum
Kesalahan umum dalam penampungan air hujan:
- Lupa menggunakan min: ketinggian air adalah
min(max_left, max_right), bukan hanya salah satunya. Sebuah batang membutuhkan dinding tinggi di kedua sisinya. - Air bernilai negatif: gunakan
max(0, ...)untuk membatasi nilai negatif menjadi 0 ketika tinggi suatu posisi melebihi ketinggian air. - Posisi tepi: batang paling kiri dan paling kanan tidak pernah dapat menampung air (tidak memiliki dinding di salah satu sisi). Pendekatan larik prefiks menangani hal ini secara alami karena
max_left[0] = height[0]membuat air selalu 0 pada indeks 0. - Larik kosong atau sangat kecil: kembalikan 0 untuk larik yang memiliki kurang dari 3 elemen.
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 waterPemeriksaan Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Rangkuman Pelajaran
Dalam pelajaran ini Anda mempelajari bahwa: penampungan air hujan diselesaikan dengan menemukan nilai minimum dari dinding kiri dan kanan tertinggi di setiap posisi, pendekatan dua penunjuk dengan ruang O(1) berhasil karena nilai maksimum berjalan di sisi yang lebih rendah selalu menjadi batasan penentu, dan pendekatan tumpukan monotonik menghitung air dalam lapisan horizontal, sehingga berguna ketika digabungkan dengan logika berbasis tumpukan lainnya. Selanjutnya kita beralih ke konsep desain sistem, dimulai dengan kerangka kerja RADIO untuk jawaban wawancara yang terstruktur.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Menampung Air Hujan: Tumpukan dan Dua Penunjuk” gratis?
Ya — teks lengkap “Menampung Air Hujan: Tumpukan dan Dua Penunjuk” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Menampung Air Hujan: Tumpukan dan Dua Penunjuk”?
Selesaikan masalah trapping-rain-water menggunakan pendekatan tumpukan monotonik yang menghitung lapisan horizontal dan pendekatan dua penunjuk yang menghitung kolom vertikal Kamu berlatih DSA 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 DSA Interview Prep?
Tidak diperlukan pengalaman sebelumnya. DSA 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 4 dari 4.
Berapa lama pelajaran “Menampung Air Hujan: Tumpukan dan Dua Penunjuk” 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 DSA Interview Prep ini?
Ya. Setiap pelajaran DSA 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