Bottom View of Binary Tree
Массив root содержит бинарное дерево: корень находится по индексу 0, потомки узла с индексом i — по индексам 2i+1 (левый) и 2i+2 (правый), -1 обозначает пустое место, а в конце могут быть дополнительные элементы -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, 1, 9, 6]
- Пояснение
- В столбце 0 находятся
5и, двумя уровнями ниже,7(индекс 4) и1(индекс 5).1расположен дальше на том же уровне, поэтому он отображается. В столбцах -2, -1, 1 и 2 находятся4,2,9и6.
- Ввод
- root = [1, 2, 3, -1, 4, -1, -1, -1, -1, 5]
- Вывод
- [5, 4, 3]
- Пояснение
- В столбце -1 находится
2, а на два уровня ниже —5(индекс 9), поэтому ты видишь5. В столбце 0 под корнем находится4. В столбце 1 находится только3.
- Ввод
- root = [8, -1, -1, -1]
- Вывод
- [8]
- Пояснение
- Каждая позиция после корня —
-1, поэтому8— единственный узел, а столбец 0 — единственный столбец.
Дополнительный вопрос
Если при равенстве на самом глубоком уровне столбца нужно возвращать все узлы с одинаковым значением, слева направо, как изменится версия с обходом в ширину? Что вернёт столбец 0 в примере 1?
Подсказки
Открывайте по одной. Каждая подсказывает чуть больше.
Пронумеруй столбцы: корень имеет номер 0, шаг влево вычитает 1, а шаг вправо прибавляет 1. Если смотреть снизу, какой узел столбца ты увидишь и что будет, если два узла этого столбца находятся на одном уровне?
Обходите узлы по уровням, слева направо внутри каждого уровня, и записывайте значение каждого узла в его столбец, заменяя то, что там было. Что останется в каждом столбце в конце?
Используйте очередь пар (индекс, столбец) и добавляйте левого потомка в очередь перед правым. Перезаписывайте значение столбца для каждого узла, отслеживайте минимальный и максимальный столбцы, а в конце считайте столбцы по порядку.
Решение
В каждом столбце показывается самый глубокий узел, а при равенстве выбирается узел, идущий позже в порядке обхода по уровням. Поиск в ширину посещает узлы в порядке обхода по уровням, поэтому ответом будет последний посещённый им узел в столбце: пусть каждый узел перезаписывает значение в ячейке своего столбца.
Поиск в глубину: самый глубокий узел в каждом столбце
Идея
Обходите дерево в глубину, передавая каждому узлу его столбец и глубину, и для каждого столбца сохраняйте глубину и значение самого глубокого узла на данный момент.
При посещении левого поддерева перед правым узлы одного уровня встречаются слева направо, поэтому нужный вам узел — последний встреченный: заменяйте его, если глубина больше или равна. В примере 1 при обходе встречаются 7, а затем 1; оба узла находятся на глубине 2 в столбце 0, и столбец занимает 1.
Столбцы образуют непрерывный диапазон, поэтому отслеживайте минимальный и максимальный и считывайте каждый столбец между ними. Один проход по каждому узлу даёт время O(n), а рекурсия достигает глубины в h вызовов для дерева из h уровней.
Алгоритм
- Посетите корень со столбцом 0 и глубиной 0.
- Пропустите индекс
i, если он выходит за пределы массива илиroot[i]равен-1. - Если в столбце ещё нет узла или эта глубина больше либо равна сохранённой, сохраните эту глубину и значение.
- Посетите
2i+1со столбцомcol-1, затем2i+2со столбцомcol+1, оба на один уровень глубже. - Верните сохранённые значения, начиная с наименьшего столбца.
def bottomView(root):
n = len(root)
best = {} # column -> (depth, value) of the deepest node found so far
def visit(i, col, depth):
if i >= n or root[i] == -1:
return
# Replace on a greater or equal depth: on a tie, the node
# met last is the one further right, which comes later 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, а на каждом уровне — слева направо. Среди узлов одного столбца последним извлекается самый глубокий, а при равной глубине — расположенный правее. Поэтому, когда каждый узел перезаписывает ячейку своего столбца, в каждой ячейке остаётся узел, видимый снизу, и сравнивать глубину не нужно.
В примере 1 узел 7 записывает значение в столбец 0, а узел 1 перезаписывает его.
Обработка каждого узла требует одного посещения в очереди и одной записи в map: время — O(n). Очередь может вместить целый уровень — примерно половину всех узлов, поэтому пространственная сложность — O(n).
Алгоритм
- Поместите пару (индекс 0, столбец 0) в очередь.
- Извлеките пару (
i,col) и сохранитеroot[i]в столбцеcol, заменив любое предыдущее значение. - Добавьте (
2i+1,col-1), затем (2i+2,col+1) для каждого дочернего узла, который находится внутри массива и не равен-1. - Повторяйте, пока очередь не опустеет, отслеживая самый левый и самый правый столбцы.
- Верните значения отображения, начиная с самого левого столбца.
from collections import deque
def bottomView(root):
n = len(root)
bottom = {} # column -> value of the last 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 deeper nodes later, and right after left
# on one level, so the last node to reach a column is the one seen from below.
bottom[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 [bottom[col] for col in range(min(bottom), max(bottom) + 1)]
Ловушки и крайние случаи
- Заменять только при строго большей глубине в версии с обходом в глубину. Поэтому в примере 1 остаётся
7, а не1. - Если при поиске в ширину оставлять первый узел для каждого столбца, получится вид сверху.
- Добавлять правого потомка в очередь перед левым. Тогда каждый уровень будет обрабатываться справа налево, и при равной глубине будет выбран неправильный узел.
- Брать в качестве ответа листья. В примере 1 лист
7скрыт узлом1, а в примере 2 у узла4есть потомок, но он находится внизу столбца 0. - Сортировать столбцы как текст. Как строки,
"-10"окажется между"-1"и"-2".
Частые вопросы3
Что такое вид бинарного дерева снизу?
Назначьте корневому узлу столбец 0, каждому шагу влево — на один столбец меньше, а каждому шагу вправо — на один столбец больше. Вид снизу показывает самый глубокий узел каждого столбца слева направо. В этой задаче при равной глубине выбирается узел, который идет позже в порядке обхода по уровням.
Какова временная сложность получения нижнего вида бинарного дерева?
O(n) для n узлов: поиск в ширину обрабатывает каждый узел один раз, выполняя одну запись в хеш-таблицу. Упорядоченная карта, например древовидная карта, добавляет множитель log w для w столбцов.
В чём разница между видом сверху и видом снизу?
В обоих случаях выбирается по одному узлу на столбец. Вид сверху выбирает самый неглубокий узел, а вид снизу — самый глубокий. В версии с поиском в ширину вид сверху сохраняет первое значение, полученное столбцом, а вид снизу перезаписывает его каждый раз.
Похожие задачи
Задачи на те же идеи. Решите две или три, и приём запомнится.
def bottomView(root):
# Напишите код здесьТест #1
Тест #2
Тест #3
Аргумент
root = [5, 2, 9, 4, 7, 1, 6]
Ожидаемый вывод
[4, 2, 1, 9, 6]