Sprawdzanie BST i właściwości inorder
Zweryfikują Państwo, czy drzewo binarne jest BST, przekazując w dół drzewa ograniczenia min/max oraz sprawdzając, czy przejście inorder tworzy posortowaną sekwencję.
Sprawdzanie BST i właściwości inorder to bezpłatna lekcja Coding Interview Prep na CoddyKit. To lekcja 3 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 Coding Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.
Problem walidacji BST
Validate BST (LeetCode #98) to klasyczne zadanie rekrutacyjne, które sprawia trudność wielu kandydatom. Naiwne podejście sprawdza tylko, czy wartość każdego węzła jest większa od jego lewego dziecka i mniejsza od prawego, ale takie lokalne sprawdzenie jest niewystarczające. Węzeł w poddrzewie może spełniać lokalną regułę, a jednocześnie naruszać globalną własność BST. Poprawne rozwiązanie przekazuje w dół drzewa prawidłowe dolne i górne granice.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# Why local check fails:
# 5
# / \
# 1 4
# / \
# 3 6
# Node 4's children (3, 6) satisfy local rule,
# but 4 < 5 and is in the RIGHT subtree -- BST violated!
print('Local check is insufficient -- use min/max bounds')Podejście z dolną i górną granicą
Należy przekazywać dolną i górną granicę w dół rekurencji. Dla każdego węzła trzeba sprawdzić, czy low < node.val < high. Przy przejściu do lewego poddrzewa należy ustawić górną granicę na node.val (wartości w lewym poddrzewie muszą być mniejsze). Przy przejściu do prawego poddrzewa należy ustawić dolną granicę na node.val (wartości w prawym poddrzewie muszą być większe). Należy rozpocząć od low = -infinity i high = +infinity.
def is_valid_bst(root, low=float('-inf'), high=float('inf')):
if not root:
return True
if not (low < root.val < high):
return False
return (is_valid_bst(root.left, low, root.val) and
is_valid_bst(root.right, root.val, high))
# Valid BST:
valid = TreeNode(5)
valid.left = TreeNode(3)
valid.right = TreeNode(7)
print(is_valid_bst(valid)) # True
# Invalid BST (3 is in wrong subtree conceptually):
invalid = TreeNode(5)
invalid.left = TreeNode(1)
invalid.right = TreeNode(4)
invalid.right.left = TreeNode(3)
invalid.right.right = TreeNode(6)
print(is_valid_bst(invalid)) # False (4 < 5 in right subtree)Walidacja za pomocą przejścia inorder
Alternatywne podejście do walidacji wykorzystuje właściwość BST polegającą na uporządkowaniu w przejściu inorder: należy zebrać sekwencję inorder i sprawdzić, czy jest ściśle rosnąca. Jest to eleganckie i łatwe do przeanalizowania rozwiązanie. Wymaga jednak O(n) dodatkowej przestrzeni na przechowywanie sekwencji. Zoptymalizowana wersja korzysta podczas przejścia z jednego wskaźnika prev, aby sprawdzać każdą parę bez zapisywania całej sekwencji.
def is_valid_bst_inorder(root):
prev = [float('-inf')]
def inorder(node):
if not node:
return True
if not inorder(node.left):
return False
if node.val <= prev[0]: # not strictly increasing
return False
prev[0] = node.val
return inorder(node.right)
return inorder(root)
valid = TreeNode(5)
valid.left = TreeNode(3)
valid.right = TreeNode(7)
valid.left.left = TreeNode(1)
valid.left.right = TreeNode(4)
print(is_valid_bst_inorder(valid)) # True
invalid = TreeNode(5)
invalid.left = TreeNode(6) # 6 > 5 in left subtree!
print(is_valid_bst_inorder(invalid)) # FalsePorównanie obu podejść do walidacji
Podejście z dolną i górną granicą ma złożoność czasową O(n) i pamięciową O(h) (na stosie wywołań przechowywane są tylko granice). Podejście z wskaźnikiem prev w przejściu inorder również ma złożoność czasową O(n) i pamięciową O(h). Oba podejścia są optymalne. Podejście z granicami jest bardziej uniwersalne i dobrze sprawdza się po rozszerzeniu o dodatkowe ograniczenia. Na rozmowie kwalifikacyjnej warto być przygotowanym na przedstawienie obu podejść i omówienie kompromisów — znajomość alternatyw jest wyraźnym atutem.
# Both approaches:
# Time: O(n) -- visit each node once
# Space: O(h) -- call stack depth
# h = O(log n) balanced, O(n) skewed
# When to choose which:
# min/max bounds:
# - Cleaner for trees with constraints beyond BST
# - No global state (purely functional)
# in-order prev:
# - More intuitive (sorted sequence check)
# - Easier to convert to iterative with a stack
print('Both O(n) time, O(h) space -- choose by clarity')Naprawa BST: dwa zamienione węzły
Recover BST (LeetCode #99) naprawia BST, w którym dokładnie dwa węzły zamieniły się miejscami. Podczas przejścia inorder poprawnie uporządkowane BST tworzy posortowaną sekwencję. Jeśli dwa węzły zostaną zamienione, pojawi się jedno lub dwa naruszenia, w których prev.val > current.val. Pierwszy węzeł pierwszego naruszenia i drugi węzeł ostatniego naruszenia to dwa nieprawidłowo umieszczone węzły — należy zamienić ich wartości.
def recover_tree(root):
first = second = prev = None
def inorder(node):
nonlocal first, second, prev
if not node:
return
inorder(node.left)
if prev and prev.val > node.val:
if not first:
first = prev # first violator
second = node # always update second
prev = node
inorder(node.right)
inorder(root)
# Swap values of the two misplaced nodes
if first and second:
first.val, second.val = second.val, first.val
root = TreeNode(3)
root.left = TreeNode(1)
root.right = TreeNode(4)
root.right.left = TreeNode(2) # 2 and 3 are swapped
recover_tree(root)
print(root.val, root.right.left.val) # 2, 3 (fixed)BST do posortowanej tablicy w kolejności inorder
Konwersja BST do posortowanej tablicy jest prosta: należy wykonać przejście inorder i zebrać wartości. Ta operacja o złożoności czasowej O(n) i pamięciowej O(n) umożliwia szybkie wykorzystanie algorytmów dla posortowanych tablic (wyszukiwania binarnego, dwóch wskaźników) na danych z BST. Często stanowi etap pośredni w wieloczęściowych zadaniach dotyczących BST, takich jak „scalanie dwóch BST” lub „znalezienie mediany BST”.
def bst_to_sorted_array(root):
result = []
def inorder(node):
if not node:
return
inorder(node.left)
result.append(node.val)
inorder(node.right)
inorder(root)
return result
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.left = TreeNode(5)
root.right.right = TreeNode(7)
print(bst_to_sorted_array(root)) # [1, 2, 3, 4, 5, 6, 7]Scalanie dwóch BST
Aby scalić dwa BST w jedną posortowaną tablicę, należy przekonwertować każde z nich na posortowaną tablicę w czasie O(n) i O(m), a następnie scalić te dwie tablice za pomocą etapu scalania algorytmu sortowania przez scalanie w czasie O(n+m). Łączna złożoność czasowa wynosi O(n+m). Jeśli wynik ma być zrównoważonym BST, należy przekazać scaloną posortowaną tablicę do algorytmu konwersji posortowanej tablicy na BST. Taki podział na proste podproblemy jest oznaką przejrzystego rozwiązania, które dobrze sprawdza się na rozmowie kwalifikacyjnej.
def merge_two_bsts(root1, root2):
def inorder(node, arr):
if not node:
return
inorder(node.left, arr)
arr.append(node.val)
inorder(node.right, arr)
arr1, arr2 = [], []
inorder(root1, arr1)
inorder(root2, arr2)
# Merge two sorted arrays
merged = []
i = j = 0
while i < len(arr1) and j < len(arr2):
if arr1[i] <= arr2[j]:
merged.append(arr1[i]); i += 1
else:
merged.append(arr2[j]); j += 1
merged.extend(arr1[i:])
merged.extend(arr2[j:])
return merged
r1 = TreeNode(2); r1.left = TreeNode(1); r1.right = TreeNode(4)
r2 = TreeNode(3); r2.left = TreeNode(0); r2.right = TreeNode(5)
print(merge_two_bsts(r1, r2)) # [0, 1, 2, 3, 4, 5]Zliczanie węzłów BST w zakresie
Należy policzyć, ile węzłów ma wartości należące do zakresu [low, high]. Naiwne przejście inorder po całym drzewie ma złożoność O(n). Wersja wykorzystująca właściwości BST stosuje przycinanie: jeśli wartość bieżącego węzła jest mniejsza niż low, nie ma potrzeby sprawdzać lewego poddrzewa (wszystkie znajdujące się w nim wartości również są mniejsze niż low). Analogicznie należy pominąć prawe poddrzewo, gdy bieżąca wartość jest większa niż high. Średnia złożoność wynosi O(log n + k), gdzie k oznacza liczbę pasujących węzłów.
def range_sum_bst(root, low, high):
if not root:
return 0
total = 0
if low <= root.val <= high:
total += root.val
if root.val > low: # left subtree may have values >= low
total += range_sum_bst(root.left, low, high)
if root.val < high: # right subtree may have values <= high
total += range_sum_bst(root.right, low, high)
return total
root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(15)
root.left.left = TreeNode(3)
root.left.right = TreeNode(7)
root.right.right = TreeNode(18)
print(range_sum_bst(root, 7, 15)) # 7 + 10 + 15 = 32Duplikaty wartości oraz ścisłe i nieścisłe BST
Standardowy niezmiennik BST wykorzystuje nierówności ścisłe: wartości w lewym poddrzewie są ściśle mniejsze, a wartości w prawym poddrzewie — ściśle większe. Niektóre zadania dopuszczają duplikaty, umieszczając je w lewym poddrzewie (left <= root) lub w prawym poddrzewie (root < right). Podczas walidacji BST zawsze należy sprawdzić definicję przyjętą w treści zadania. Podejście z dolną i górną granicą obsługuje oba warianty, zmieniając to, czy sprawdzanie granic ma być ścisłe, czy nieścisłe.
# Strict BST (LeetCode default): left < root < right
def is_valid_strict(root, lo=float('-inf'), hi=float('inf')):
if not root:
return True
if not (lo < root.val < hi): # STRICT inequalities
return False
return (is_valid_strict(root.left, lo, root.val) and
is_valid_strict(root.right, root.val, hi))
# Non-strict BST (allows duplicates in right): left <= root < right
def is_valid_nonstrict(root, lo=float('-inf'), hi=float('inf')):
if not root:
return True
if not (lo <= root.val < hi): # NOTE: <= for left side
return False
return (is_valid_nonstrict(root.left, lo, root.val + 1) and
is_valid_nonstrict(root.right, root.val, hi))
print('Always clarify strict vs non-strict with interviewer')Przejście inorder jako uniwersalne narzędzie BST
Przejście inorder jest uniwersalnym narzędziem w zadaniach dotyczących BST. Gdy zadanie dotyczące BST odnosi się do kolejności posortowanej, k-tego elementu, zapytań o zakres lub właściwości sekwencji, warto rozważyć, czy odpowiedzi nie dostarczy przejście inorder (lub jego odwrotność). Większość problemów specyficznych dla BST sprowadza się do następującego schematu: przejść w kolejności posortowanej i wykonać operację na każdym kroku. Szybkie rozpoznawanie tego schematu jest ważną umiejętnością podczas rozmów kwalifikacyjnych.
# Problems solved elegantly with in-order:
# 1. Validate BST: check prev <= curr during in-order
# 2. Kth smallest: count k steps in in-order
# 3. Kth largest: count k steps in REVERSE in-order
# 4. Closest value to target: find crossover in in-order
# 5. BST to sorted array: collect in-order into list
# 6. Recover BST: find 1-2 violations in in-order
# 7. Sum of range [lo, hi]: accumulate during in-order
# The key insight: in-order visits BST nodes in sorted order.
# All sorted-order reasoning translates to in-order DFS.
print('In-order = sorted access = foundation of BST reasoning')Najbliższa wartość w BST
Należy znaleźć węzeł, którego wartość jest najbliższa podanej wartości docelowej. Warto wykorzystać uporządkowanie BST: rozpocząć od korzenia, śledzić dotychczas znalezioną najbliższą wartość i poruszać się w kierunku wartości docelowej (przejść w lewo, jeśli wartość docelowa jest mniejsza, lub w prawo, jeśli jest większa). To podejście o złożoności O(h) jest wydajniejsze niż przejście inorder i pokazuje skuteczne wykorzystanie własności BST do ograniczania przestrzeni wyszukiwania.
def closest_value(root, target):
closest = root.val
curr = root
while curr:
if abs(curr.val - target) < abs(closest - target):
closest = curr.val
if target < curr.val:
curr = curr.left
elif target > curr.val:
curr = curr.right
else:
break # exact match
return closest
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(5)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
print(closest_value(root, 3.714286)) # 4Sprawdzenie wiedzy
Proszę sprawdzić, czy rozumieją Państwo pojęcia z lekcji Data Structures & Algorithms — Coding Interview Prep.
Podsumowanie lekcji
W tej lekcji poznali Państwo: walidację BST z użyciem dolnej i górnej granicy (zapobiegającą problemowi lokalnego sprawdzania), alternatywę z wskaźnikiem prev w przejściu inorder oraz przejście inorder jako uniwersalne narzędzie do obliczania sum zakresów, znajdowania najbliższej wartości i scalania. W dalszej części wykorzystamy właściwości przejścia inorder BST do znalezienia k-tego najmniejszego elementu.
Często zadawane pytania
Czy lekcja „Sprawdzanie BST i właściwości inorder” jest bezpłatna?
Tak — pełny tekst „Sprawdzanie BST i właściwości inorder” 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 Coding Interview Prep, przejdź na CoddyKit PRO. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Sprawdzanie BST i właściwości inorder”?
Zweryfikują Państwo, czy drzewo binarne jest BST, przekazując w dół drzewa ograniczenia min/max oraz sprawdzając, czy przejście inorder tworzy posortowaną sekwencję. Ćwiczysz Coding 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ąć Coding Interview Prep?
Nie wymagamy żadnego doświadczenia. Coding 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 3 z 4.
Ile czasu zajmuje lekcja „Sprawdzanie BST i właściwości inorder”?
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 Coding Interview Prep?
Tak. Każda lekcja Coding 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
- Wstawianie i wyszukiwanie w BST
- Usuwanie z BST: trzy przypadki
- Sprawdzanie BST i właściwości inorder
- K-ty najmniejszy element, suma zakresu i BST do posortowanej tablicy