Implementacja stosu i zastosowania
Zaimplementują Państwo stos z operacjami push/pop/peek, a następnie rozwiążą zadania valid-parentheses, min-stack i evaluate reverse-polish notation.
Implementacja stosu i zastosowania to bezpłatna lekcja DSA Interview Prep na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DSA Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Struktura danych stosu
Stos to struktura danych działająca zgodnie z zasadą last-in, first-out (LIFO). Ostatni element odłożony na stos jest pierwszym elementem z niego zdejmowanym. Można wyobrazić go sobie jako stos talerzy: elementy można dodawać i usuwać wyłącznie z góry. Podstawowe operacje to push (dodanie na górze), pop (usunięcie z góry) i peek (odczytanie elementu z góry bez usuwania). Wszystkie trzy operacje mają złożoność O(1) w poprawnie zaimplementowanym stosie.
W Pythonie lista doskonale sprawdza się jako stos: append odpowiada operacji push, pop() operacji pop, a [-1] operacji peek.
stack = []
# Push
stack.append(10)
stack.append(20)
stack.append(30)
print('After pushes:', stack) # [10, 20, 30]
# Peek
print('Top:', stack[-1]) # 30
# Pop
print('Popped:', stack.pop()) # 30
print('After pop:', stack) # [10, 20]Klasa stosu z operacjami Push, Pop, Peek i isEmpty
Umieszczenie listy w klasie zapewnia przejrzystszy interfejs i zapobiega przypadkowemu użyciu operacji, które nie należą do stosu, takich jak insert lub indeksowanie pozycji innych niż górny element. Jest to implementacja, jakiej oczekują rekruterzy, gdy proszą o „zaimplementowanie stosu od podstaw”.
class Stack:
def __init__(self):
self._data = []
def push(self, val):
self._data.append(val)
def pop(self):
if self.is_empty():
raise IndexError('pop from empty stack')
return self._data.pop()
def peek(self):
if self.is_empty():
raise IndexError('peek at empty stack')
return self._data[-1]
def is_empty(self):
return len(self._data) == 0
def __len__(self):
return len(self._data)
s = Stack()
s.push(1); s.push(2); s.push(3)
print(s.peek()) # 3
print(s.pop()) # 3
print(len(s)) # 2Poprawne nawiasy (LeetCode 20)
LeetCode 20 „Valid Parentheses”: określenie, czy ciąg nawiasów jest poprawnie zbilansowany. Dla każdego nawiasu otwierającego należy wykonać push. Dla każdego nawiasu zamykającego należy sprawdzić, czy element na szczycie stosu jest odpowiadającym mu nawiasem otwierającym; jeśli nie albo jeśli stos jest pusty, należy zwrócić False. Jeśli na końcu stos jest pusty, ciąg jest poprawny. To klasyczne pierwsze zastosowanie stosu podczas rozmów kwalifikacyjnych dotyczących programowania.
def isValid(s):
stack = []
matching = {')': '(', '}': '{', ']': '['}
for ch in s:
if ch in '([{':
stack.append(ch)
else:
if not stack or stack[-1] != matching[ch]:
return False
stack.pop()
return len(stack) == 0
print(isValid('()[]{}')) # True
print(isValid('([)]')) # False
print(isValid('{[]}')) # True
print(isValid(']')) # FalseStos minimum (LeetCode 155)
LeetCode 155 „Min Stack”: zaprojektowanie stosu obsługującego operacje push, pop, peek i getMin, każdą w czasie O(1). Sztuczka polega na utrzymywaniu drugiego stosu, który w każdej chwili przechowuje minimum. Podczas dodawania elementu należy dodać go również na stos minimum, jeśli nowa wartość jest <= bieżącego minimum (albo jeśli stos minimum jest pusty). Podczas usuwania elementu należy usunąć go również ze stosu minimum, jeśli usunięta wartość jest równa bieżącemu minimum.
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val):
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self):
val = self.stack.pop()
if val == self.min_stack[-1]:
self.min_stack.pop()
return val
def top(self):
return self.stack[-1]
def getMin(self):
return self.min_stack[-1]
ms = MinStack()
ms.push(-2); ms.push(0); ms.push(-3)
print(ms.getMin()) # -3
ms.pop()
print(ms.top()) # 0
print(ms.getMin()) # -2Obliczanie odwrotnej notacji polskiej
LeetCode 150 „Evaluate Reverse Polish Notation” (notacja postfiksowa): operandy są odkładane na stos; po napotkaniu operatora należy zdjąć dwa operandy, zastosować operator i odłożyć wynik. Kolejność ma znaczenie przy odejmowaniu i dzieleniu: pierwszy zdjęty operand jest prawym operandem, a drugi — lewym.
def evalRPN(tokens):
stack = []
ops = set(['+', '-', '*', '/'])
for tok in tokens:
if tok not in ops:
stack.append(int(tok))
else:
b = stack.pop() # right operand
a = stack.pop() # left operand
if tok == '+':
stack.append(a + b)
elif tok == '-':
stack.append(a - b)
elif tok == '*':
stack.append(a * b)
else: # division truncated toward zero
stack.append(int(a / b))
return stack[0]
print(evalRPN(['2','1','+','3','*'])) # 9
print(evalRPN(['4','13','5','/','+'])) # 6
print(evalRPN(['10','6','9','3','+','-11','*','/','*','17','+','5','+'])) # 22Dekodowanie ciągu (LeetCode 394)
LeetCode 394 „Decode String”: mając zakodowany ciąg, taki jak 3[a2[c]], należy rozwinąć go do postaci accaccacc. Należy użyć dwóch stosów: jednego dla liczników powtórzeń i drugiego dla zgromadzonych ciągów. Po napotkaniu cyfry należy zbudować pełną liczbę. Po napotkaniu [ należy odłożyć bieżący ciąg i licznik. Po napotkaniu ] należy zdjąć te wartości ze stosów i powtórzyć bieżący fragment. Po napotkaniu litery należy dodać ją do bieżącego ciągu.
def decodeString(s):
count_stack = []
str_stack = []
current_str = ''
current_num = 0
for ch in s:
if ch.isdigit():
current_num = current_num * 10 + int(ch)
elif ch == '[':
count_stack.append(current_num)
str_stack.append(current_str)
current_str = ''
current_num = 0
elif ch == ']':
repeats = count_stack.pop()
current_str = str_stack.pop() + current_str * repeats
else:
current_str += ch
return current_str
print(decodeString('3[a]2[bc]')) # 'aaabcbc'
print(decodeString('3[a2[c]]')) # 'accaccacc'
print(decodeString('2[abc]3[cd]ef')) # 'abcabccdcdcdef'Dzienne temperatury (wprowadzenie do stosu monotonicznego)
LeetCode 739 „Daily Temperatures”: dla każdego dnia należy znaleźć liczbę dni do wystąpienia wyższej temperatury. Naiwne rozwiązanie ma złożoność O(n²). W rozwiązaniu ze stosem należy przechodzić przez temperatury; dla każdego dnia zdejmować ze stosu wszystkie elementy (indeksy dni), których temperatura jest niższa niż temperatura dzisiejsza. Odpowiedzią dla każdego zdjętego dnia jest (today - popped_day). Następnie należy odłożyć bieżący dzień. Pozostałe elementy stosu nigdy nie doczekały się wyższej temperatury — ich odpowiedź wynosi 0.
def dailyTemperatures(temps):
result = [0] * len(temps)
stack = [] # stores indices
for i, t in enumerate(temps):
while stack and temps[stack[-1]] < t:
j = stack.pop()
result[j] = i - j
stack.append(i)
return result
print(dailyTemperatures([73,74,75,71,69,72,76,73]))
# [1, 1, 4, 2, 1, 1, 0, 0]Stos w przechodzeniu DFS
Stos wywołań używany w rekurencyjnym DFS można zastąpić jawnym stosem, przekształcając algorytm w iteracyjny. Należy odłożyć korzeń; dopóki stos nie jest pusty, zdejmować węzeł, przetwarzać go i odkładać jego dzieci (najpierw prawe, potem lewe, aby przetwarzać je od lewej do prawej). Iteracyjny DFS zachowuje się tak samo jak rekurencyjny DFS, ale pozwala uniknąć limitu rekurencji Pythona w przypadku głębokich drzew.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def preorder_iterative(root):
if not root:
return []
result, stack = [], [root]
while stack:
node = stack.pop()
result.append(node.val)
if node.right:
stack.append(node.right) # push right first
if node.left:
stack.append(node.left) # so left is processed first
return result
root = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)), TreeNode(3))
print(preorder_iterative(root)) # [1, 2, 4, 5, 3]Złożoność czasowa i pamięciowa
Wszystkie operacje stosu (push, pop, peek, isEmpty) mają zamortyzowaną złożoność O(1). Zbudowanie stosu zawierającego n elementów zajmuje O(n). W najgorszym przypadku stos zajmuje O(n) pamięci, gdy przechowywane są wszystkie elementy. W zadaniach wykorzystujących stos monotoniczny każdy element jest odkładany i zdejmowany najwyżej raz, co daje łączny czas O(n) we wszystkich iteracjach — a nie O(n²), jak mogłoby sugerować naiwne odczytanie zewnętrznej pętli.
# Demonstrate O(n) total for monotonic stack
# Each element pushed once, popped at most once => 2n operations total
def count_ops(n):
pushes = pops = 0
stack = []
for i in range(n):
while stack and stack[-1] < i: # simulated decreasing condition
stack.pop()
pops += 1
stack.append(i)
pushes += 1
return pushes, pops
p, pp = count_ops(1000)
print(f'Pushes: {p}, Pops: {pp}, Total ops: {p+pp}') # <= 2000Największy prostokąt w histogramie (wprowadzenie)
LeetCode 84 „Largest Rectangle in Histogram” to najtrudniejsze z klasycznych zadań wykorzystujących stos. Dla każdego słupka prostokąt, którego może on być podstawą, rozciąga się w lewo do napotkania niższego słupka i w prawo do napotkania niższego słupka. Stos monotoniczny przechowuje indeksy słupków uporządkowanych rosnąco według wysokości. Po napotkaniu niższego słupka należy zdejmować elementy i obliczać prostokąt o wysokości zdjętego słupka. Stos pozwala znaleźć lewą i prawą granicę w czasie O(1) dla każdego zdejmowanego elementu.
def largestRectangleArea(heights):
stack = [] # indices, increasing heights
result = 0
heights = heights + [0] # sentinel forces all pops
for i, h in enumerate(heights):
while stack and heights[stack[-1]] > h:
height = heights[stack.pop()]
width = i if not stack else i - stack[-1] - 1
result = max(result, height * width)
stack.append(i)
return result
print(largestRectangleArea([2,1,5,6,2,3])) # 10
print(largestRectangleArea([2,4])) # 4Strategia rozwiązywania zadań stosowych podczas rozmowy kwalifikacyjnej
Zadania stosowe często ukrywają się pod postacią „przetwarzania od środka na zewnątrz” albo „znajdowania następnego większego/mniejszego elementu”. O użyciu stosu mogą świadczyć następujące sygnały: potrzebują Państwo ostatnio napotkanego elementu, dopasowują Państwo pary (nawiasy, znaczniki) albo chcą Państwo uzyskać O(n) w zadaniu, które naiwnie wymaga zagnieżdżonych pętli O(n²). W szczególności stosy monotoniczne przekształcają zadanie „dla każdego elementu znajdź najbliższy większy/mniejszy” z O(n²) w O(n).
Podczas rozmowy kwalifikacyjnej należy jasno określić niezmiennik stosu: „Będę utrzymywać stos indeksów w kolejności malejących wysokości”.
Szybki test
Sprawdź swoją wiedzę z zagadnień Data Structures & Algorithms — Coding Interview Prep omówionych w tej lekcji.
Podsumowanie lekcji
W tej lekcji nauczyłeś się, że: listy Pythona implementują operacje push/pop/peek w czasie O(1), dzięki czemu idealnie nadają się na stosy, poprawne nawiasy i stos minimum to dwa klasyczne zadania stosowe podczas rozmów kwalifikacyjnych, a stosy monotoniczne rozwiązują zadania typu next-greater-element w czasie O(n), ponieważ każdy element jest odkładany i zdejmowany najwyżej raz. W następnej części zbudujemy kolejki za pomocą deque z Pythona i rozwiążemy zadanie o maksimum w przesuwanym oknie.
Często zadawane pytania
Czy lekcja „Implementacja stosu i zastosowania” jest bezpłatna?
Tak — pełny tekst „Implementacja stosu i zastosowania” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DSA Interview Prep, przejdź na CoddyKit PRO. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Implementacja stosu i zastosowania”?
Zaimplementują Państwo stos z operacjami push/pop/peek, a następnie rozwiążą zadania valid-parentheses, min-stack i evaluate reverse-polish notation. Ćwiczysz DSA Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć DSA Interview Prep?
Nie wymagamy żadnego doświadczenia. DSA Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Implementacja stosu i zastosowania”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji DSA Interview Prep?
Tak. Każda lekcja DSA Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Implementacja stosu i zastosowania
- Implementacja kolejki i deque
- Wzorzec stosu monotonicznego
- Wzajemna symulacja stosu i kolejki