Diameter of Binary Tree
Otrzymujesz drzewo binarne zapisane w tablicy tree w kolejności poziomami. Korzeń znajduje się pod indeksem 0, dzieci węzła 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 wpisami -1. Zwróć średnicę drzewa: liczbę krawędzi na najdłuższej ścieżce między dowolnymi dwoma węzłami. Ścieżka może przechodzić przez korzeń lub pozostać w obrębie jednego poddrzewa.
Funkcja
- treeinteger-array
- drzewo binarne w kolejności poziomów, z wartością -1 oznaczającą puste miejsce
- Zwracainteger
- liczba krawędzi na najdłuższej ścieżce między dwoma węzłami
Ograniczenia
1 ≤ tree.length ≤ 32767- Każde
tree[i]ma wartość-1lub wartość spełniającą warunek0 ≤ tree[i] ≤ 1000. tree[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 głębokość wynosi najwyżej
14.
Przykłady
- Wejście
- tree = [8, 3, 6, 1, 4, -1, -1, -1, -1, 7]
- Wyjście
- 4
- Wyjaśnienie
- Ścieżka
7,4,3,8,6(indeksy9,4,1,0,2) zawiera pięć węzłów połączonych czterema krawędziami. Skręca przy korzeniu: trzy krawędzie w dół po lewej stronie i jedna w dół po prawej.
- Wejście
- tree = [2, 5, -1, 1, 9, -1, -1, 3, -1, -1, 4]
- Wyjście
- 4
- Wyjaśnienie
- Ścieżka
3,1,5,9,4ma cztery krawędzie i skręca przy5o indeksie1. Korzeń nie ma prawego dziecka, więc ścieżka przechodząca przez korzeń ma tylko trzy krawędzie w dół jego lewej strony.
- Wejście
- tree = [6, -1, -1]
- Wyjście
- 0
- Wyjaśnienie
- Pojedynczy węzeł nie ma krawędzi. Najdłuższa ścieżka to sam węzeł, o długości
0.
+12 ukrytych testów przy wysłaniu
Pytanie dodatkowe
Jak zwrócić samą ścieżkę, czyli wartości węzłów od jednego końca średnicy do drugiego?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Każda ścieżka w drzewie ma jeden najwyższy węzeł, w którym kierunek zmienia się z w górę na w dół. Gdybyś znał ten węzeł, jak długa mogłaby być ścieżka, która przez niego przechodzi?
Ścieżka, która skręca w węźle
i, schodzi w dół do lewego poddrzewa i w dół do prawego. Jej długość wynosi co najwyżej wysokość lewego dziecka plus wysokość prawego dziecka, gdzie wysokość oznacza liczbę węzłów na najdłuższej ścieżce w dół, a puste miejsce ma wysokość0.Oblicz wysokości od dołu w jednym przebiegu w porządku postorder: wysokość węzła to
1 + max(left, right). Gdy masz już wartościleftirightdla węzła, zaktualizuj wynik, dodającleft + right.
Rozwiązanie
Najdłuższa ścieżka nie musi przechodzić przez korzeń, więc zmierzenie obu stron korzenia nie wystarczy. Każda ścieżka ma najwyższy węzeł, w którym skręca, przechodząc od wędrówki w górę do wędrówki w dół, a najdłuższa ścieżka skręcająca w danym węźle ma długość równą sumie wysokości jego lewego i prawego poddrzewa. Jedno przejście post-order oblicza każdą wysokość od dołu i sprawdza każdy punkt skrętu po drodze, w O(n).
Zmierz każdą parę węzłów
Poprawne, ale nie kończy się na największych testach
Intuicja
Najpierw: jak poruszać się po tablicy. Węzeł o indeksie i ma lewe dziecko pod indeksem 2*i+1, a prawe dziecko pod indeksem 2*i+2, więc jego rodzic znajduje się pod indeksem (i-1)/2, zaokrąglonym w dół. Miejsce jest rzeczywiste tylko wtedy, gdy jego indeks mieści się w tablicy, a jego wartość nie jest równa -1. W [8, 3, 6, 1, 4, -1, -1, -1, -1, 7] węzeł 7 o indeksie 9 ma rodzica pod indeksem 4, a ten 4 ma rodzica pod indeksem 1.
Średnica to największa odległość między dwoma węzłami, więc możesz zmierzyć każdą parę. Aby obliczyć odległość między indeksami a i b, wspinaj się w stronę korzenia krok po kroku, aż się spotkają, za każdym razem zaczynając od większego indeksu. Większy indeks nigdy nie znajduje się na wyższym poziomie, więc ten krok nigdy nie minie miejsca spotkania. Liczba kroków to liczba krawędzi. Dla 9 i 2: 9 wspina się do 4, a potem do 1, 2 wspina się do 0, a 1 wspina się do 0. Cztery kroki.
To rozwiązanie jest poprawne, ale powolne. Największy test to pełne drzewo o 16383 węzłach, co daje około 1.3 × 10^8 par, a każda para wymaga do 26 kroków. Miliardy kroków dla jednej odpowiedzi to znacznie więcej, niż pozwala limit czasu.
Algorytm
- Zbierz indeksy wszystkich rzeczywistych węzłów.
- Dla każdej pary
(a, b)ustawedges = 0i powtarzaj, aża == b: zastąp większy indeks jego rodzicem i dodaj1doedges. - Zachowaj największą wartość
edges, jaką zobaczysz, i zwróć ją.
def diameterOfBinaryTree(tree):
nodes = [i for i, value in enumerate(tree) if value != -1]
best = 0
for x in range(len(nodes)):
for y in range(x + 1, len(nodes)):
a, b = nodes[x], nodes[y]
edges = 0
while a != b:
# A larger index is never higher up, so climb from it.
if a > b:
a = (a - 1) // 2
else:
b = (b - 1) // 2
edges += 1
best = max(best, edges)
return bestZmierz obie wysokości w każdym węźle
Intuicja
Przyjrzyj się najdłuższej ścieżce od jej najwyższego węzła — tego, w którym przestaje ona prowadzić w górę i zaczyna prowadzić w dół. Stamtąd prowadzi ona jak najniżej lewą stroną i jak najniżej prawą stroną. Niech height(c) zlicza węzły na najdłuższej ścieżce w dół od c, przyjmując 0 dla pustego miejsca. Wtedy najdłuższa ścieżka skręcająca w węźle i ma height(2*i+1) + height(2*i+2) krawędzi, po jednej krawędzi dla każdego z tych węzłów.
Wypróbuj więc każdy węzeł jako punkt skrętu i zachowaj najlepszy wynik. W drugim przykładzie 5 o indeksie 1 ma wysokość 2 po lewej stronie (1, 3) i 2 po prawej stronie (9, 4), co daje ścieżkę o długości czterech krawędzi. Korzeń ma wysokość 3 po lewej stronie i 0 po prawej, co daje tylko trzy krawędzie.
Każde wywołanie height przechodzi przez całe poddrzewo, a do węzła wraca się ponownie dla każdego jego przodka, więc złożoność wynosi O(n·h). Przy h ≤ 14 to wystarczająco szybkie rozwiązanie, ale w drzewie wskaźnikowym o kształcie łańcucha h może osiągnąć n, a wtedy ta sama metoda ma złożoność O(n²). Powtarzające się wywołania height to zbędna praca, którą eliminuje ostatnie podejście.
Algorytm
- Napisz
height(i):0dla pustego miejsca, w przeciwnym razie1 + max(height(2*i+1), height(2*i+2)). - Dla każdego rzeczywistego węzła
iobliczheight(2*i+1) + height(2*i+2). - Zwróć największą z tych sum.
def diameterOfBinaryTree(tree):
n = len(tree)
def height(i):
# Nodes on the longest downward path from i; an empty spot has 0.
if i >= n or tree[i] == -1:
return 0
return 1 + max(height(2 * i + 1), height(2 * i + 2))
best = 0
for i in range(n):
if tree[i] != -1:
# The longest path that turns at node i goes down both sides.
best = max(best, height(2 * i + 1) + height(2 * i + 2))
return bestJedno przejście po wysokościach w kolejności postorder
Intuicja
Wysokość węzła zależy tylko od wysokości jego dwojga dzieci, a to właśnie te same dwie liczby są potrzebne do sprawdzenia punktu zwrotnego. Oblicz je więc raz, od dołu do góry. Przejście w porządku post-order kończy przetwarzanie obojga dzieci, zanim przetworzy ich rodzica. W każdym węźle masz wtedy left i right: zaktualizuj wynik, używając left + right, a rodzicowi przekaż 1 + max(left, right).
W pierwszym przykładzie liść 7 zwraca 1, znajdujący się nad nim węzeł 4 zwraca 2, a węzeł 3 zwraca 3, ponieważ jego drugie dziecko 1 ma wysokość 1. Węzeł 6 zwraca 1. W korzeniu left + right = 3 + 1 = 4 — to jest wynik. Najlepszy wynik, jaki może dać dowolny inny węzeł, to 3, przy 1 + 2 = 3.
Każdy węzeł jest odwiedzany raz, więc czas działania wynosi O(n), a rekurencja sięga tak głęboko jak drzewo: O(h), czyli około jednej ramki na poziom. Wynik jest przechowywany w zmiennej poza rekurencją, ponieważ to, co zwraca wywołanie (wysokość), różni się od tego, czego potrzebujesz na końcu (długości ścieżki).
Algorytm
- Ustaw
best = 0i napiszheight(i). Dla pustego miejsca zwróć0. - Oblicz
left = height(2*i+1)iright = height(2*i+2). - Ustaw
bestna większą z wartościbestileft + right. - Zwróć
1 + max(left, right). - Wywołaj
height(0)i zwróćbest.
def diameterOfBinaryTree(tree):
n = len(tree)
best = 0
def height(i):
# Returns the height of node i and updates best on the way back up.
nonlocal best
if i >= n or tree[i] == -1:
return 0
left = height(2 * i + 1)
right = height(2 * i + 2)
best = max(best, left + right) # the longest path that turns at node i
return 1 + max(left, right)
height(0)
return best
Pułapki i przypadki brzegowe
Większość błędnych odpowiedzi wynika z liczenia niewłaściwych rzeczy lub pomiaru w niewłaściwym węźle.
- Liczenie węzłów zamiast krawędzi. Ścieżka
7,4,3,8,6ma pięć węzłów i długość4, a średnica pojedynczego węzła wynosi0. - Mierzenie tylko ścieżek przechodzących przez korzeń. W drugim przykładzie najlepsza ścieżka przechodząca przez korzeń ma trzy krawędzie, a odpowiedź wynosi cztery i zmienia kierunek na indeksie
1. - Zwracanie średnicy z wywołania rekurencyjnego. Rodzic potrzebuje wysokości swoich dzieci, aby budować dłuższe ścieżki; średnica powinna być przechowywana w osobnej zmiennej.
- Mieszanie dwóch konwencji liczenia wysokości. Gdy wysokość oznacza liczbę węzłów, a dla pustego miejsca przyjmujemy
0, wyrażenieleft + rightod razu daje liczbę krawędzi. Gdy wysokość oznacza liczbę krawędzi, dla pustego miejsca trzeba przyjąć-1, a wynik obliczyć jakoleft + right + 2. Połączenie połowy jednej konwencji z połową drugiej daje wynik różniący się o jeden lub dwa. - Odczytywanie danych poza końcem tablicy. Liść znajdujący się blisko końca tablicy może mieć indeksy dzieci wykraczające poza ostatni element. Traktuj indeks wykraczający poza koniec jako puste miejsce.
- Pomylenie przesunięcia w Lua i R, gdzie tablice zaczynają się od 1. Zachowaj indeksy węzłów liczone od 0 dla obliczeń
2*i+1i odczytujtree[i + 1].
Najczęstsze pytania4
Jaka jest złożoność czasowa problemu średnicy drzewa binarnego?
Rozwiązanie w porządku post-order odwiedza każdy węzeł raz, więc działa w czasie O(n) i wykorzystuje dodatkową przestrzeń O(h) na rekurencję, gdzie h oznacza wysokość. Obliczanie wysokości osobno dla każdego węzła kosztuje O(n·h), co w przypadku drzewa przypominającego łańcuch daje O(n²).
Czy średnica drzewa binarnego zawsze przechodzi przez korzeń?
Nie. Najdłuższa ścieżka może w całości przebiegać wewnątrz jednego poddrzewa, na przykład gdy korzeń ma jedną krótką gałąź, a po drugiej stronie głębokie, rozłożyste poddrzewo. Dlatego sprawdzasz left + right w każdym węźle, a nie tylko w korzeniu.
Czy średnicę liczy się w węzłach czy w krawędziach?
Tutaj średnicę liczy się w krawędziach, czyli połączeniach między kolejnymi węzłami na ścieżce, dlatego pojedynczy węzeł ma średnicę 0, a dwa połączone węzły mają średnicę 1. W niektórych książkach liczy się węzły, co daje wynik większy o jeden. Zanim dodasz lub odejmiesz 1, sprawdź, o którą definicję pyta zadanie.
Jak znaleźć średnicę drzewa binarnego bez użycia rekurencji?
Odwiedź węzły w kolejności, w której każde dziecko pojawia się przed swoim rodzicem. Jeden ze sposobów: umieść korzeń na stosie, zdejmuj węzły i dodawaj je do listy, jednocześnie umieszczając ich dzieci na stosie, a następnie przejdź przez tę listę w odwrotnej kolejności. Zapisz wysokość każdego węzła w tablicy, odczytaj wysokości dwojga dzieci w każdym węźle i zaktualizuj wynik, dodając je do siebie. Złożoność czasowa pozostaje O(n).
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
Python
def diameterOfBinaryTree(tree):
# Wpisz kod tutajPrzypadek 1
Przypadek 2
Przypadek 3
Wejście
tree = [8, 3, 6, 1, 4, -1, -1, -1, -1, 7]
Oczekiwane
4