Vertical Order Traversal of a Binary Tree
Drzewo binarne jest przechowywane w tablicy root w kolejności kopcowej: korzeń znajduje się pod indeksem 0, dzieci elementu o indeksie i znajdują się pod indeksami 2*i+1 (lewe) i 2*i+2 (prawe), -1 oznacza puste miejsce, a tablica może kończyć się dodatkowymi elementami -1.
Korzeń znajduje się w kolumnie 0; lewe dziecko znajduje się o jedną kolumnę w lewo od rodzica, a prawe dziecko o jedną kolumnę w prawo. Zwróć po jednej liście dla każdej kolumny, od lewej do prawej. W obrębie kolumny wymień węzły od góry do dołu, a węzły znajdujące się w tej samej kolumnie i na tej samej głębokości — w kolejności rosnącej wartości.
Funkcja
- rootinteger-array
- drzewo w porządku kopcowym, z -1 oznaczającym puste miejsce
- Zwracainteger-2d-array
- jedna lista wartości na kolumnę, zaczynając od kolumny skrajnie lewej, każda od góry do dołu
Ograniczenia
1 ≤ root.length ≤ 32767- Każde
root[i]ma wartość-1lub wartość spełniającą warunek0 ≤ root[i] ≤ 5000, a żadna wartość nie występuje dwukrotnie. root[0]nigdy nie jest równe-1, więc drzewo ma co najmniej jeden węzeł.- Tablica może kończyć się dodatkowymi wpisami
-1za ostatnim węzłem. - Oboje dzieci pustego miejsca również są puste, a drzewo ma co najwyżej
14poziomów.
Przykłady
- Wejście
- root = [5, 2, 9, 4, 7, 1, 6]
- Wyjście
- [[4], [2], [5, 1, 7], [9], [6]]
- Wyjaśnienie
- Kolumna 0 zawiera korzeń
5, a dwa poziomy niżej znajdują się7i1. Te dwie wartości mają tę samą kolumnę i głębokość, więc1występuje przed7, mimo że7znajduje się bardziej na lewo.
- Wejście
- root = [1, 2, 3, -1, 4, -1, -1, -1, -1, 5]
- Wyjście
- [[2, 5], [1, 4], [3]]
- Wyjaśnienie
- Kolumna -1 zawiera
2na głębokości 1 i5na głębokości 3 (lewe dziecko4). Kolumna 0 zawiera korzeń1i4pod indeksem 4. Kolumna 1 zawiera tylko3.
- Wejście
- root = [6, -1, -1]
- Wyjście
- [[6]]
- Wyjaśnienie
6nie ma dzieci, więc odpowiedź ma jedną kolumnę:[6].
Pytanie dodatkowe
Każda wartość jest nie większa niż 5000. Czy możesz wykorzystać tę informację, aby pominąć sortowanie porównawcze i zakończyć działanie w czasie O(n + 5000)?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Każdy węzeł ma kolumnę, głębokość i wartość. Gdyby wszystkie te trójki znajdowały się na jednej liście, w jakiej kolejności umieściłbyś je w odpowiedzi?
Przeszukiwanie wszerz już porządkuje węzły od góry do dołu. Której części kolejności nadal brakuje, gdy kilka węzłów znajduje się w tej samej kolumnie i na tym samym poziomie?
Przetwarzaj drzewo poziom po poziomie. Sortuj każdy poziom według kolumny, a następnie według wartości, dodawaj każdą wartość do listy jej kolumny, a na końcu odczytuj listy od najmniejszej kolumny do największej.
Rozwiązanie
Każdy węzeł ma trzy klucze: kolumna, głębokość i wartość. Odpowiedzią są węzły uporządkowane według tych kluczy, po jednej liście na kolumnę. W przypadku remisów w kolumnie i głębokości decyduje wartość, a nie kolejność od lewej do prawej, w jakiej węzły napotyka algorytm przechodzenia, więc musisz sortować: wszystkie węzły naraz albo każdy poziom wyszukiwania wszerz.
Zapisz każdy węzeł, a następnie posortuj
Intuicja
Odwiedź każdy węzeł raz, w dowolnej kolejności, i zapisz trójkę (kolumna, głębokość, wartość). Przejście do dziecka zwiększa głębokość o 1 i przesuwa kolumnę o 1 w lewo lub w prawo. Następnie posortuj trójki według kolumny, głębokości i wartości. Węzły z każdej kolumny znajdą się teraz obok siebie, w wymaganej kolejności. Przejdź po posortowanej liście i rozpocznij nową listę wewnętrzną za każdym razem, gdy zmieni się kolumna.
W przykładzie 1 trójki z kolumny 0 po sortowaniu to (0, 0, 5), (0, 2, 1), (0, 2, 7). Sortowanie o złożoności O(n log n) dominuje nad przejściem o złożoności O(n), a trójki zajmują O(n) pamięci.
Algorytm
- Umieść (indeks 0, kolumna 0, głębokość 0) na stosie.
- Zdejmij węzeł ze stosu, zapisz (kolumna, głębokość, wartość) i umieść każde rzeczywiste dziecko z kolumną
col-1lubcol+1i głębokościądepth+1na stosie. - Gdy stos będzie pusty, posortuj trójki według kolumny, następnie głębokości, a na końcu wartości.
- Przejdź przez nie, rozpoczynając nową listę za każdym razem, gdy zmieni się kolumna.
def verticalTraversal(root):
n = len(root)
entries = [] # (column, depth, value) for every node
stack = [(0, 0, 0)] # (node index, column, depth); the root is never empty
while stack:
i, col, depth = stack.pop()
entries.append((col, depth, root[i]))
for child, child_col in ((2 * i + 1, col - 1), (2 * i + 2, col + 1)):
if child < n and root[child] != -1:
stack.append((child, child_col, depth + 1))
# Column, then depth, then value: exactly the order of the answer.
entries.sort()
result = []
for k, (col, depth, value) in enumerate(entries):
if k == 0 or col != entries[k - 1][0]:
result.append([]) # a new column starts
result[-1].append(value)
return resultPoziom po poziomie, sortuj w obrębie każdego poziomu
Intuicja
Przeszukiwanie wszerz przetwarza po jednym poziomie naraz. Jeśli każdy węzeł dopisuje swoją wartość do listy swojej kolumny podczas przetwarzania, każda lista jest już uporządkowana od góry do dołu, więc nie trzeba sortować według głębokości. Remisy występują tylko w obrębie jednego poziomu, dlatego przed jego przetworzeniem posortuj każdy poziom według kolumny, a następnie wartości.
W przykładzie 1 poziom 2 pojawia się jako 4, 7, 1, 6 w kolumnach -2, 0, 0, 2 i zostaje posortowany do postaci 4, 1, 7, 6, więc kolumna 0 przyjmuje wartość [5, 1, 7]. Zajęte kolumny tworzą jeden nieprzerwany zakres, więc wystarczy śledzić najmniejszą i największą.
Sortowanie poziomu zawierającego m węzłów kosztuje O(m log m). Złożoność w najgorszym przypadku nadal wynosi O(n log n), ponieważ ostatni poziom pełnego drzewa zawiera około połowy węzłów. Złożoność pamięciowa wynosi O(n).
Algorytm
- Zacznij od poziomu zawierającego (indeks 0, kolumna 0).
- Posortuj poziom według kolumny, a następnie wartości.
- W tej kolejności dodaj każdą wartość do listy jej kolumny i zbierz jej rzeczywiste dzieci, z kolumnami
col-1icol+1, do następnego poziomu. - Powtarzaj, aż poziom będzie pusty, śledząc najmniejszą i największą kolumnę.
- Zwróć listy od najmniejszej kolumny do największej.
def verticalTraversal(root):
n = len(root)
columns = {} # column -> its values, top to bottom
level = [(0, 0)] # (node index, column) of every node on the current level
while level:
# Every node here has the same depth: order them by column, then value.
level.sort(key=lambda node: (node[1], root[node[0]]))
next_level = []
for i, col in level:
columns.setdefault(col, []).append(root[i])
for child, child_col in ((2 * i + 1, col - 1), (2 * i + 2, col + 1)):
if child < n and root[child] != -1:
next_level.append((child, child_col))
level = next_level
# The columns that hold a node form one unbroken range.
return [columns[col] for col in range(min(columns), max(columns) + 1)]
Pułapki i przypadki brzegowe
- Pozostawianie remisów w kolejności od lewej do prawej. Wyszukiwanie wszerz bez sortowania na każdym poziomie daje dla kolumny 0 z przykładu 1 wynik
[5, 7, 1], a nie[5, 1, 7]. - Dodawanie wartości podczas przechodzenia w głąb bez sortowania. W
[1, 2, 3, -1, 4, -1, -1, -1, -1, -1, 5]przejście najpierw w lewo dodaje5(głębokość 3) do kolumny 1 przed3(głębokość 1). - Sortowanie według kolumny i wartości, ale nie głębokości. Kolumna 0 z przykładu 1 zawiera wtedy
[1, 5, 7]. - Używanie ciągów znaków jako kluczy kolumn. Przy sortowaniu tekstowym
"-1"znajduje się przed"-2", więc kolumny na lewo od 0 pojawiają się w niewłaściwej kolejności.
Najczęstsze pytania3
Jaka jest złożoność czasowa pionowego przechodzenia drzewa binarnego?
Sortowanie wszystkich węzłów według kolumny, głębokości i wartości kosztuje O(n log n) i dominuje nad przejściem O(n). Sortowanie poziom po poziomie ma taki sam pesymistyczny przypadek, ponieważ ostatni poziom pełnego drzewa zawiera około połowy węzłów. Złożoność pamięciowa wynosi O(n).
Czy można wykonać przejście w kolejności pionowej bez sortowania?
Tylko wtedy, gdy remisy rozstrzyga się od lewej do prawej. Wtedy przeszukiwanie wszerz, które dodaje każdą wartość do listy jej kolumny, daje wynik w czasie O(n). Gdy remisy rozstrzyga się według wartości, potrzebujesz sortowania, ale tylko w obrębie jednego poziomu, ponieważ remisować mogą tylko węzły na tym samym poziomie.
Jaki jest związek między przechodzeniem w kolejności pionowej a widokiem z góry i widokiem z dołu?
Wszystkie trzy grupują węzły według tych samych numerów kolumn. Widok z góry zachowuje najpłytszy węzeł w każdej kolumnie, a widok z dołu — najgłębszy. Przejście w kolejności pionowej zachowuje wszystkie węzły i porządkuje według wartości te, które znajdują się na tym samym poziomie, podczas gdy widoki wybierają węzły na podstawie ich położenia.
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
def verticalTraversal(root):
# Wpisz kod tutajTest #1
Test #2
Test #3
Argument
root = [5, 2, 9, 4, 7, 1, 6]
Oczekiwany wynik
[[4], [2], [5, 1, 7], [9], [6]]