Top View of Binary Tree
Дано двоичное дерево, хранящееся в массиве root в порядке кучи: корень находится по индексу 0, потомки узла с индексом i — по индексам 2*i+1 (левый) и 2*i+2 (правый), а для пустого места используется -1.
Корень находится в столбце 0; левый потомок расположен на один столбец левее родителя, а правый — на один столбец правее. Если смотреть сверху, виден самый мелкий узел в каждом столбце; если таких узлов несколько, выбирайте тот, который первым встречается на своём уровне слева направо (с наименьшим индексом). Верните значения видимых узлов от самого левого столбца к самому правому.
Функция
- rootinteger-array
- дерево в порядке кучи, где -1 обозначает пустое место
- Возвращаетinteger-array
- значение, видимое сверху в каждом столбце, начиная с самого левого столбца
Ограничения
1 ≤ root.length ≤ 32767- Каждый
root[i]равен-1или значению в диапазоне0 ≤ root[i] ≤ 104, и ни одно значение не встречается дважды. root[0]никогда не равно-1, поэтому в дереве есть как минимум один узел.- Массив может заканчиваться дополнительными элементами
-1после последнего узла. - Оба потомка пустого места тоже пусты, а дерево имеет не более
14уровней.
Примеры
- Ввод
- root = [5, 2, 9, 4, 7, 1, 6]
- Вывод
- [4, 2, 5, 9, 6]
- Пояснение
- Слева направо в столбцах находятся
4(столбец -2),2(-1), корень5(0),9(1) и6(2).7и1тоже находятся в столбце 0, но корень расположен выше них.
- Ввод
- root = [1, 2, -1, -1, 3, -1, -1, -1, -1, -1, 4]
- Вывод
- [2, 1, 4]
- Пояснение
2находится в столбце -1. Его правый потомок3с индексом 4 снова находится в столбце 0, под корнем, поэтому он скрыт.4с индексом 10, правый потомок3, находится один в столбце 1.
- Ввод
- root = [6, -1, -1]
- Вывод
- [6]
- Пояснение
- Индексы 1 и 2 содержат
-1, поэтому6находится отдельно в столбце 0 и представляет собой весь вид.
Дополнительный вопрос
Когда вместо массива используются объекты-узлы, индекса нет, чтобы разрешить равенство. Как бы ты сформулировал правило разрешения равенства и изменится ли твой код обхода в ширину?
Подсказки
Открывайте по одной. Каждая подсказывает чуть больше.
Пронумеруй столбцы: корень имеет номер 0, шаг влево вычитает 1, а шаг вправо прибавляет 1. Какой узел в столбце виден сверху и как отличить его от остальных узлов этого столбца?
Если обходить узлы уровень за уровнем и слева направо внутри каждого уровня, какой узел столбца встретится первым?
Выполни поиск в ширину по парам (index, column), добавляя сначала левого потомка, а затем правого. Сохраняй значение только при первом появлении столбца, отслеживай минимальный и максимальный столбцы, а в конце считай столбцы по порядку.
Решение
В каждом столбце показан узел, находящийся на наименьшей глубине, а при равенстве глубин — тот, который встречается первым при обходе по уровням. Это в точности порядок, в котором поиск в ширину посещает узлы, поэтому первый узел, который поиск встречает в столбце, и есть ответ. Обход в глубину тоже подойдёт, но для этого нужно отслеживать глубину и внимательно разрешать равенства.
Поиск в глубину: самый глубокий узел в каждом столбце
Идея
Обходи дерево в глубину, отслеживая для каждого узла его столбец и глубину, и храни для каждого столбца глубину и значение самого верхнего из найденных узлов.
Заменяй значение только при строго меньшей глубине. Из двух узлов с одинаковой глубиной первым при обходе в порядке уровней будет достигнут тот, который первым встречается при обходе в глубину, исследующем каждое левое поддерево перед соответствующим правым: в точке расхождения двух путей он идёт налево. Если заменять значение при равной глубине, победит более поздний узел: если индекс 3 пуст, а узлы находятся по индексам 19 и 23, оба узла расположены в столбце -2 на глубине 4, и ответом будет узел с индексом 19.
Занятые столбцы образуют непрерывный диапазон, поскольку у узла в столбце -3 есть предки в столбцах -1 и -2, поэтому отслеживай минимальный и максимальный столбцы. Каждый узел посещается один раз, время работы — O(n), а глубина рекурсии составляет примерно h вызовов.
Алгоритм
- Посети корень со столбцом 0 и глубиной 0. Сразу вернись, если индекс выходит за пределы массива или содержит
-1. - Если для столбца ещё нет узла или эта глубина строго меньше сохранённой, сохрани эту глубину и значение.
- Посети
2i+1со столбцомcol-1, затем2i+2со столбцомcol+1на уровень глубже. - Верни сохранённые значения от наименьшего столбца к наибольшему.
def topView(root):
n = len(root)
best = {} # column -> (depth, value) of the shallowest node found so far
def visit(i, col, depth):
if i >= n or root[i] == -1:
return
# Replace only on a strictly smaller depth: on a tie, the node
# met first is the one further left, which comes first in level order.
if col not in best or depth < best[col][0]:
best[col] = (depth, root[i])
visit(2 * i + 1, col - 1, depth + 1)
visit(2 * i + 2, col + 1, depth + 1)
visit(0, 0, 0)
# The columns that hold a node form one unbroken range.
return [best[col][1] for col in range(min(best), max(best) + 1)]Поиск в ширину, первый узел в каждом столбце
Идея
Очередь, которая принимает корень, затем левого потомка каждого узла, а потом правого, выдаёт узлы в порядке уровней: сначала все узлы глубины d, а затем любой узел глубины d+1, причём на каждом уровне — слева направо. В этом и состоит приоритет задачи, поэтому первый узел, полученный столбцом, — тот, который виден сверху, а все последующие пропускаются.
Для каждого узла требуется один поиск в map, всего — O(n). Очередь может содержать целый уровень — до примерно половины узлов, поэтому объём памяти составляет O(n).
Алгоритм
- Помести пару (индекс 0, столбец 0) в очередь.
- Извлеки пару (
i,col). Если в карте нет записи дляcol, сохрани тамroot[i]. - Добавь (
2i+1,col-1), затем (2i+2,col+1) для каждого существующего потомка. - Когда очередь опустеет, верни значения карты от столбца с наименьшим номером до столбца с наибольшим.
from collections import deque
def topView(root):
n = len(root)
top = {} # column -> value of the first node seen in that column
queue = deque([(0, 0)]) # (node index, column); the root is never empty
while queue:
i, col = queue.popleft()
# The queue hands out shallower nodes first, and left before right
# on one level, so the first node of a column is the one seen from above.
if col not in top:
top[col] = root[i]
left, right = 2 * i + 1, 2 * i + 2
if left < n and root[left] != -1:
queue.append((left, col - 1))
if right < n and root[right] != -1:
queue.append((right, col + 1))
# The columns that hold a node form one unbroken range.
return [top[col] for col in range(min(top), max(top) + 1)]
Ловушки и крайние случаи
- Если сохранять первый найденный узел при обходе в глубину. Если у левого потомка корня есть правый потомок, у которого есть правый потомок (столбец 1, глубина 3), обход встретит его раньше правого потомка корня (столбец 1, глубина 1).
- Если сначала помещать в очередь правого потомка: при равенстве приоритетов выбирается не тот узел.
- Если сортировать столбцы как текст:
"-1"сортируется перед"-2". - Если предполагать, что отрицательные столбцы заполняет левое поддерево. В примере 2 узел
4находится в левом поддереве корня и в столбце 1.
Частые вопросы3
Что такое вид бинарного дерева сверху?
Пронумеруйте столбцы от корня, начиная с 0: на единицу меньше для левого потомка и на единицу больше для правого потомка. Вид сверху показывает для каждого столбца слева направо узел, ближайший к корню.
Какова временная сложность получения вида сверху бинарного дерева?
Поиск в ширину посещает каждый из n узлов один раз, выполняя поиск в отображении за O(1) для каждого узла, поэтому он работает за O(n), если отслеживать минимальный и максимальный столбцы. Упорядоченное отображение столбцов обеспечивает сложность O(n log w) для w столбцов.
Чем вид сверху отличается от вида слева и вида снизу?
Вид слева выбирает первый узел на каждом уровне. Вид сверху выбирает самый близкий к корню узел в каждом столбце. Вид снизу выбирает самый удалённый от корня узел в каждом столбце, поэтому поиск в ширину для него перезаписывает каждый столбец, а не сохраняет первый узел.
Похожие задачи
Задачи на те же идеи. Решите две или три, и приём запомнится.
def topView(root):
# Напишите код здесьТест #1
Тест #2
Тест #3
Аргумент
root = [5, 2, 9, 4, 7, 1, 6]
Ожидаемый вывод
[4, 2, 5, 9, 6]