Рубрики
Без рубрики

Решение: сплющивание двоичного дерева в связанный список

Это является частью серии объяснений решений LeetCode (индекс). Если вам понравилось это решение или fou … с меткой алгоритмы, JavaScript, Java, Python.

Это является частью серии объяснений решения 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”