Trapping Rain Water: stack och två pekare
Lös trapping-rain-water med både metoden med monoton stack, som beräknar horisontella lager, och metoden med två pekare, som beräknar vertikala kolumner.
Trapping Rain Water: stack och två pekare är en gratis lektion i Förberedelse inför kodningsintervjuer på CoddyKit. Detta är lektion 4 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Förberedelse inför kodningsintervjuer, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.
Problem: Fånga regnvatten
Fånga regnvatten (LeetCode 42) är ett av de mest ikoniska intervjuproblemen. Givet n icke-negativa heltal som representerar en höjdkarta där varje stapel har bredden 1 ska du beräkna hur mycket vatten som kan samlas mellan staplarna efter att det har regnat. Vatten samlas i varje sänka mellan högre staplar på båda sidor.
För varje position i är vattennivån min(max_left[i], max_right[i]) - height[i]. Om detta är negativt samlas inget vatten (stapeln är högre än minst en av gränserna). Det finns tre angreppssätt: förberäknade arrayer O(n)/O(n), två pekare O(n)/O(1) och monoton stack 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)Angreppssätt 1: Förberäknade maximumarrayer
Den raka lösningen med tidskomplexiteten O(n) och utrymmeskomplexiteten O(n) förberäknar två arrayer: max_left[i] = den största höjden från index 0 till i och max_right[i] = den största höjden från index i till n-1. Vattenmängden vid position i är max(0, min(max_left[i], max_right[i]) - height[i]).
Att bygga max_left kräver en enda genomgång från vänster till höger; max_right kräver en genomgång från höger till vänster. En sista genomgång summerar vattnet. Detta angreppssätt är rent och enkelt att förklara, men använder O(n) extra utrymme.
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])) # 9Angreppssätt 2: Två pekare (O(1) utrymme)
Tvåpekaralgoritmen uppnår tidskomplexiteten O(n) och utrymmeskomplexiteten O(1). Använd vänster- och högerpekare som börjar i varsin ände. Upprätthåll max_left och max_right som löpande maximumvärden från respektive sida.
Bearbeta i varje steg den sida som har det mindre löpande maximumvärdet — eftersom den sidan är den begränsande faktorn. Om max_left < max_right är vattnet vid vänsterpekaren max_left - height[left] (den högra sidan är tillräckligt hög). Flytta vänsterpekaren inåt. Bearbeta annars högerpekaren på motsvarande sätt. Inga förberäknade arrayer behövs.
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])) # 3Varför två pekare fungerar: invarianten
Den viktiga insikten är följande: när vi bearbetar vänsterpekaren eftersom height[left] < height[right] vet vi att max_right >= height[right] > height[left]. Därför är den effektiva vattenbarriären till höger minst height[right], som redan är större än max_left. Alltså är min(max_left, effective_max_right) = max_left, och vattenformeln förenklas till max_left - height[left].
Vi behöver inte känna till det exakta max_right — det räcker att veta att det är minst height[right] > height[left] för att kunna använda max_left som vattennivå. Detta är den eleganta invariant som gör O(1) utrymme möjligt.
# 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)Metod 3: Monoton stack (horisontella lager)
Metoden med en monoton stack beräknar vatten i horisontella lager mellan intilliggande staplar. Håll en monotont avtagande stack med index. När stapel i är högre än stackens topp j uppstår en sänka: botten är height[j], den vänstra väggen är height[stack[-1]] efter att j tagits bort och den högra väggen är height[i]. Vattnet fyller sänkan upp till min(left_wall, right_wall) - floor, med bredden i - stack[-1] - 1.
Varje ”sänka” beräknas när en högre stapel påträffas. Då bearbetas vattnet i avgränsade rektangulära segment, vilket är användbart när du även behöver hålla reda på vilka staplar som bidrar till vattennivån.
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])) # 9Följ den monotona stacken
Låt oss följa [0,1,0,2,1,0,1,3,...] med stackmetoden. När vi når stapel 3 (h=2) vid i=3 är stackens topp i=2 (h=0), så den tas bort. Den vänstra väggen är i=1 (h=1), och den högra väggen är h=2. Vattenhöjd = min(1,2)-0=1, bredd=3-1-1=1, area=1. Fortsätt: stackens topp i=1 (h=1) är inte mindre än 2, så vi stoppar. Lägg 3 på stacken.
Stackmetoden är mer komplex att implementera än tvåpekarmetoden, men visar vilka specifika staplar som bildar varje vattencell. Denna insikt är användbar i följdfrågor om att återskapa vattnets utbredning eller räkna olika sänkor.
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)Jämförelse av alla tre metoder
Sammanfattning av de tre metoderna för att samla regnvatten:
- Prefixarrayer: O(n) tid, O(n) minnesutrymme. Enklast att förstå och verifiera. Bäst i intervjuer där tydlighet värderas högre än minneseffektivitet.
- Två pekare: O(n) tid, O(1) minnesutrymme. Optimalt vad gäller både tid och minnesutrymme. Bäst för följdfrågor i stil med ”kan du använda O(1) minnesutrymme?”
- Monoton stack: O(n) tid, O(n) minnesutrymme. Bearbetar vatten i horisontella lager. Bäst när du behöver veta vilka staplar som bidrar eller när problemet ingår som delproblem i en större stackbaserad algoritm.
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}')Behållaren med mest vatten
Container With Most Water (LeetCode 11) förväxlas ofta med problemet att samla regnvatten. Här väljer du exakt två staplar, och vattnet begränsas enbart av dessa två staplar (interna staplar spelar ingen roll). Maximera arean min(height[l], height[r]) × (r - l).
Två pekare löser detta girigt: börja i båda ändarna (maximal bredd). Flytta den kortare pekaren inåt — att flytta den högre kan bara minska arean. Detta tar O(n) tid och O(1) minnesutrymme och är enklare än tvåpekarlösningen för att samla regnvatten, eftersom inget löpande maxvärde behövs.
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 mapAvancerat: Trapping Rain Water II (3D)
Trapping Rain Water II (LeetCode 407) utvidgar problemet till en 2D-höjdmatris. Vatten kan rinna i alla fyra riktningar och måste rinna ut över kanten. Lösningen använder en min-heap: initiera heapen med alla celler på kanten och utför sedan en BFS-liknande expansion. Bearbeta cellen med lägst höjd — varje lägre granne måste innehålla vatten åtminstone upp till den aktuella cellens nivå.
Detta är en grundläggande annorlunda algoritm än i 1D-fallet och testar både heapoperationer och BFS-traversering. Tvåpekarttricket från 1D generaliseras inte till 2D, men heapmetoden gör det.
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)) # 4När de olika metoderna används i intervjuer
Beslutsvägledning för intervjun om att samla regnvatten:
- Börja med: prefixarrayer — lätta att förklara, visuellt intuitiva och uppenbart korrekta
- Följdfrågan ”O(1) minnesutrymme?”: två pekare — förklara invarianten att den mindre sidan är flaskhalsen
- Om intervjuaren frågar ”finns det någon annan metod?”: monoton stack — förklara beräkningen av horisontella lager
Börja alltid med att tydligt definiera vad som avgör vattennivån vid varje position (det minsta av den högsta stapeln på vardera sidan) innan du går över till koden. Detta visar att du har förstått problemet och gör lösningen enklare att förklara.
# 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()Randfall och vanliga misstag
Vanliga misstag när man löser problemet att samla regnvatten:
- Glömma min: vattennivån är
min(max_left, max_right), inte bara en av dem. En stapel behöver höga väggar på båda sidor. - Negativt vatten: använd
max(0, ...)för att begränsa negativa värden till 0 när en positions höjd överstiger vattennivån. - Positioner vid kanterna: staplarna längst till vänster och höger kan aldrig hålla vatten (ingen vägg på ena sidan). Prefixarraymetoden hanterar detta naturligt eftersom
max_left[0] = height[0]gör att vattnet alltid är 0 vid index 0. - Tomma eller mycket små arrayer: returnera 0 för arrayer med färre än 3 element.
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 waterSnabbtest
Testa din förståelse av begreppen Data Structures & Algorithms — Coding Interview Prep från den här lektionen.
Sammanfattning av lektionen
I den här lektionen har du lärt dig: problemet att samla regnvatten löses genom att hitta det minsta av de högsta vänstra och högra väggarna vid varje position, metoden med två pekare och O(1) minnesutrymme fungerar eftersom det löpande maxvärdet på den sida som är lägre alltid är den begränsande faktorn, och metoden med en monoton stack beräknar vatten i horisontella lager, vilket är användbart när den kombineras med annan stackbaserad logik. Härnäst går vi över till systemdesignkoncept, med RADIO-ramverket för strukturerade intervjusvar som utgångspunkt.
Lär dig Förberedelse inför kodningsintervjuer med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 90
- Lektioner
- 360
Vanliga frågor
Är lektionen ”Trapping Rain Water: stack och två pekare” gratis?
Ja – hela texten till ”Trapping Rain Water: stack och två pekare” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Förberedelse inför kodningsintervjuer, kan Ni uppgradera till CoddyKit PRO. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.
Vad lär jag mig i ”Trapping Rain Water: stack och två pekare”?
Lös trapping-rain-water med både metoden med monoton stack, som beräknar horisontella lager, och metoden med två pekare, som beräknar vertikala kolumner. Ni övar på Förberedelse inför kodningsintervjuer med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Förberedelse inför kodningsintervjuer?
Du behöver inga förkunskaper. Utbildningen i Förberedelse inför kodningsintervjuer på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.
Hur lång tid tar lektionen ”Trapping Rain Water: stack och två pekare”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Förberedelse inför kodningsintervjuer-lektionen?
Ja. Varje Förberedelse inför kodningsintervjuer-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Monoton stack: ökande eller minskande
- Största rektangeln i ett histogram
- Maximalt värde i ett glidande fönster med monoton deque
- Trapping Rain Water: stack och två pekare