Это является частью серии объяснений решения LeetCode ( index ). Если вам понравилось это решение или нашли его полезным, Пожалуйста, нравится Этот пост и/или upvote Мое решение по сообщению на форумах LeetCode Анкет
Проблема LeetCode #114 (Средняя): сплющивание двоичного дерева до связанного списка
Описание:
( прыгнуть в : Идея решения Код : JavaScript | Python | Java | C ++
Учитывая корень
бинарного дерева, сплющите дерево в «связанный список»:
- «Связанный список» должен использовать то же самое
TriEnode
класс, гдесправа
Дочерний указатель указывает на следующий узел в списке ислева
Указатель ребенка всегдаNULL
Анкет - «Связанный список» должен быть в том же порядке, что и Предварительный заказ переход бинарного дерева.
Примеры:
Вход: | root = [1,2,5,3,4, NULL, 6] |
Выход: | [1, NULL, 2, NULL, 3, NULL, 4, NULL, 5, NULL, 6] |
Визуальный: |
Вход: | root = [] |
Выход: | [] |
Вход: | root = [0] |
Выход: | [0] |
Ограничения:
- Количество узлов в дереве находится в диапазоне
[0, 2000]
. -100.val
Идея:
( Прыгните к : Описание задачи Код : JavaScript | Python | Java | C ++
Morris Traversal (O (1) пространство, O (n) время) Подход:
Там это На самом деле способ пересечь двоичное дерево с Сложность пространства из O (1) оставаясь на Сложность времени из НА) , хотя это требует изменения структуры дерева. В этой проблеме, которая специально требуется, так что это действительный подход, хотя не всегда будет целесообразно изменять двоичное дерево источника в других ситуациях.
Подход называется Моррис Траверс Анкет В своей основе он использует базовую природу упорядоченных обходов, чтобы итерация через дерево. В Предварительный заказ переход двоичного дерева, каждая вершина обрабатывается в (Узел, слева, справа) порядок. Это означает, что весь левый поддерек может быть размещен между узлом и его правым поддереем.
Для этого, однако, нам сначала придется найти последний узел в левом поддерете. Это достаточно просто, поскольку мы знаем, что последний узел дерева предварительного заказа может быть найдена, перемещая вправо как можно много раз из его корня.
Таким образом, мы должны быть в состоянии пройти через двоичное дерево, отслеживая узел курильщика ( curr ). Всякий раз, когда мы находим левый поддерево, мы можем отправить бегун Чтобы найти его последний узел, затем сочините оба конца левого поддерева в правый путь Curr , принимая внимания, чтобы разорвать левое соединение в Curr Анкет
Как только это будет сделано, мы можем продолжать двигаться Curr Справа, ищу следующее левое поддерево. Когда Curr не может двигаться прямо, дерево будет успешно сплющено.
- Сложность времени: O (n) куда N это количество узлы в бинарном дереве
- Сложность пространства: O (1)
O (1) Космический подход:
Чтобы правильно подключить Связанный список , нам нужно начать с дна и работать. Это означает, что нам нужно перейти в обратный Предварительный заказ переход заказ через двоичное дерево Анкет Поскольку прохождение предварительного заказа обычно (Узел, слева, справа) , нам придется двигаться в обратном порядке (справа, слева, узел) Анкет
Чтобы завершить это решение в O (1) пространство , мы не сможем удобно отступить через стек , таким образом, ключом к этому решению будет отступление на полную ставку к корень Каждый раз, когда мы достигаем листа. Это подтолкнет Сложность времени к O (n^2) Анкет
Мы захотим сначала настроить голова и Curr Чтобы отслеживать главу связанного списка, который мы строим и текущий узел, который мы посещаем. Мы будем знать, что мы закончили один раз голова .
Чтобы следовать обратному приказу обхода в предварительном заказе, мы сначала попытаемся пойти направо, а затем влево. Поскольку мы возвращаемся к корень Однако мы в конечном итоге вернемся в тот же узел, который мы установили как голова делая это. Чтобы предотвратить это, мы остановимся до Переезд в голова Узел и разорвала соединение.
Теперь, когда мы не можем столкнуться с уже завершенной территорией, мы можем быть уверены, что любой лист, к которому мы перемещаемся, должен быть следующим значением для голова , поэтому мы должны подключить его к старому голова , обновление голова , и сбросить обратно в корень Анкет
Как отмечалось ранее, один раз голова , мы закончили нашу прохождение и можем выйти из функции.
- Сложность времени: O (n^2) куда N это количество узлы в двоичном дереве, из -за повторного возврата к корне
- Сложность пространства: O (1)
Рекурсивный подход:
Чтобы правильно подключить Связанный список , нам нужно начать с дна и работать. Это означает, что нам нужно перейти в обратный Предварительный заказ переход заказ через двоичное дерево Анкет Поскольку прохождение предварительного заказа обычно (Узел, слева, справа) , нам придется двигаться в обратном порядке (справа, слева, узел) Анкет
Бинарный обход дерева является основной почвой для рекурсивный Решение, поэтому давайте определим помощника ( Revpreorder ) для этой цели. Мы также сохраним глобальную переменную голова Чтобы отслеживать главу связанного списка, когда мы работаем назад.
Согласно нашему обратному предварительному заказанному подходу, мы хотим сначала рекурсивно пройти по праму пути, затем левый путь, если они существуют. Как только мы рекурсивно выплющили левый и правый путь, голова На этом этапе будет равен следующий узел после текущего, поэтому мы должны установить его как Узел.right . Мы не должны забывать установить Node.left к NULL , также.
Как только мы закончим с текущим узлом, мы можем обновить голова к Узел и позвольте рекурсии завершить и вернуться к следующему слою. Как только рекурсионная стек исчерпана, голова будет равен корень очередной раз.
Наконец, нам приходится иметь дело с краевым случаем пустого корень , чтобы мы могли просто убедиться, что только вызовут начальную рекурсию на корень Если корень на самом деле узел. Нет необходимости в возврат Заявление, потому что тестовый набор оценит корень напрямую.
- Сложность времени: O (n) куда N это количество узлы в бинарном дереве
- Сложность пространства: O (n) для рекурсия стека , что так же дольше максимальной глубины бинарного дерева, которое может подняться до N
Код JavaScript:
( Прыгните к : Описание задачи Идея решения
w/Morris Traversal:
var flatten = function(root) { let curr = root while (curr) { if (curr.left) { let runner = curr.left while (runner.right) runner = runner.right runner.right = curr.right, curr.right = curr.left, curr.left = null } curr = curr.right } };
без (1) пространство:
var flatten = function(root) { let head = null, curr = root while (head != root) { if (curr.right === head) curr.right = null if (curr.left === head) curr.left = null if (curr.right) curr = curr.right else if (curr.left) curr = curr.left else curr.right = head, head = curr, curr = root } };
w/Recursion:
var flatten = function(root) { let head = null const revPreOrder = node => { if (node.right) revPreOrder(node.right) if (node.left) revPreOrder(node.left) node.left = null, node.right = head, head = node } if (root) revPreOrder(root) };
Код Python:
( Прыгните к : Описание задачи Идея решения
w/Morris Traversal:
class Solution: def flatten(self, root: TreeNode) -> None: curr = root while curr: if curr.left: runner = curr.left while runner.right: runner = runner.right runner.right, curr.right, curr.left = curr.right, curr.left, None curr = curr.right
без (1) пространство:
class Solution: def flatten(self, root: TreeNode) -> None: head, curr = None, root while head != root: if curr.right == head: curr.right = None if curr.left == head: curr.left = None if curr.right: curr = curr.right elif curr.left: curr = curr.left else: curr.right, head, curr = head, curr, root
w/Recursion:
class Solution: head = None def flatten(self, root: TreeNode) -> None: def revPreOrder(node: TreeNode) -> None: if node.right: revPreOrder(node.right) if node.left: revPreOrder(node.left) node.left, node.right, self.head = None, self.head, node if root: revPreOrder(root)
Код Java:
( Прыгните к : Описание задачи Идея решения
w/Morris Traversal:
class Solution { public void flatten(TreeNode root) { TreeNode curr = root; while (curr != null) { if (curr.left != null) { TreeNode runner = curr.left; while (runner.right != null) runner = runner.right; runner.right = curr.right; curr.right = curr.left; curr.left = null; } curr = curr.right; } } }
без (1) пространство:
class Solution { public void flatten(TreeNode root) { TreeNode head = null, curr = root; while (head != root) { if (curr.right == head) curr.right = null; if (curr.left == head) curr.left = null; if (curr.right != null) curr = curr.right; else if (curr.left != null) curr = curr.left; else { curr.right = head; head = curr; curr = root; } } } }
w/Recursion:
class Solution { TreeNode head = null; public void flatten(TreeNode root) { if (root != null) revPreOrder(root); } private void revPreOrder(TreeNode node) { if (node.right != null) revPreOrder(node.right); if (node.left != null) revPreOrder(node.left); node.left = null; node.right = head; head = node; } }
C ++ Код:
( Прыгните к : Описание задачи Идея решения
w/Morris Traversal:
class Solution { public: void flatten(TreeNode* root) { TreeNode* curr = root; while (curr) { if (curr->left) { TreeNode* runner = curr->left; while (runner->right != nullptr) runner = runner->right; runner->right = curr->right, curr->right = curr->left, curr->left = nullptr; } curr = curr->right; } } };
без (1) пространство:
class Solution { public: void flatten(TreeNode* root) { TreeNode *head = nullptr, *curr = root; while (head != root) { if (curr->right == head) curr->right = nullptr; if (curr->left == head) curr->left = nullptr; if (curr->right) curr = curr->right; else if (curr->left) curr = curr->left; else curr->right = head, head = curr, curr = root; } } };
w/Recursion:
class Solution { public: void flatten(TreeNode* root) { if (root) revPreOrder(root); } private: TreeNode* head = nullptr; void revPreOrder(TreeNode* node) { if (node->right) revPreOrder(node->right); if (node->left) revPreOrder(node->left); node->left = nullptr, node->right = head, head = node; } };
Оригинал: “https://dev.to/seanpgallivan/solution-flatten-binary-tree-to-linked-list-599p”