К этапу 10

Этап 10 · урок 2

DFS дерева: что возвращает рекурсия

Полезный рекурсивный вызов возвращает родителю краткое резюме поддерева, а не просто «обходит» его.

Язык кода

После урока вы сможете

  • точно определять смысл возвращаемого значения
  • выводить переход и базу из определения состояния

В цепочке 1→2→3 узел 3 возвращает высоту 1, узел 2 — 2, корень — 3; каждый родитель использует только два ответа детей.

Как вычислить высоту дерева снизу вверх?

Найти высоту дерева как число узлов на самом длинном пути от корня до листа.

Пересчёт глубины для каждого пути от корня

Перечислить все пути до листьев, сохранить каждый путь и выбрать самый длинный.

Почему одни поддеревья нельзя обходить повторно?

Пути копируют общие части и хранят гораздо больше информации, чем требуется для высоты.

Родителю достаточно высот двух детей

Родителю нужны только две величины: высота левого и правого поддеревьев.

Что возвращает вызов height для каждого поддерева?

height(node) возвращает точную высоту поддерева node; для null высота равна 0.

База null и объединение двух ответов

Рекурсивно получить leftHeight и rightHeight, вернуть 1 + max(...). База null возвращает 0.

Высота дерева на C++17 и Python 3

C++17

#include <algorithm>
#include <cassert>
using namespace std;

struct Node {
    int value;
    Node* left;
    Node* right;
};

int height(Node* node) {
    if (node == nullptr) return 0;
    return 1 + max(height(node->left), height(node->right));
}

int main() {
    Node leaf{3, nullptr, nullptr};
    Node child{2, &leaf, nullptr};
    Node root{1, &child, nullptr};
    assert(height(&root) == 3);
    assert(height(nullptr) == 0);
}

Python 3

class Node:
    def __init__(
        self,
        value: int,
        left: "Node | None" = None,
        right: "Node | None" = None,
    ) -> None:
        self.value = value
        self.left = left
        self.right = right


def height(node: Node | None) -> int:
    if node is None:
        return 0
    return 1 + max(height(node.left), height(node.right))


assert height(Node(1, Node(2, Node(3)))) == 3
assert height(None) == 0

Один визит на узел и стек глубины h

O(n) времени и O(h) стека. В Python глубокая цепочка может превысить лимит рекурсии; итеративный BFS/DFS избегает этого.

Пустое дерево, лист и несбалансированная цепочка

Пустое дерево -> 0; один узел -> 1; только одна ветвь; две ветви разной глубины.

Тесты на разные формы дерева

null, один узел, полное дерево высоты 2, цепочка из четырёх узлов.

Сигнал: ответ родителя складывается из ответов детей

Ответ для узла можно вычислить из небольших ответов его детей.

Когда глобальное состояние только мешает?

Не используйте глобальную переменную, если задача естественно возвращает значение; глобальное состояние усложняет повторные вызовы.

Мини-проверка: почему height(null) равна нулю?

Вопрос: почему база null равна 0? Ответ: тогда лист получает 1+max(0,0)=1, что соответствует определению.

Мини-проверка: высота в узлах или в рёбрах?

Вопрос: что изменится при высоте в рёбрах? Ответ: базу часто задают -1 или отдельно определяют пустое дерево; определение должно быть единым.

Подпишите возвраты от листьев к корню

Для дерева высот поддеревьев 2 и 4 вычислите возврат корня и подпишите возврат каждого листа.

Проверьте баланс дерева одним DFS

Задача 1

Для каждого узла вычислите, сбалансировано ли его поддерево по высоте, не пересчитывая высоты повторно.

Хороший DFS возвращает родителю ровно нужное резюме

Сначала одним предложением определите возвращаемое значение; переход и база должны прямо следовать из этого определения.

Не начат

Закрепление

Попробовать самостоятельно

Небольшой маршрут от разбора к самостоятельному решению. Статусы синхронизируются с общей практикой.

  1. С разборомLeetCode · внешняя задачаСложность LeetCode: EasyУровень AlgoDS: Разминка

    Maximum Depth of Binary Tree

    Определи смысл возвращаемого значения для поддерева до записи рекуррентной формулы.

    Сначала завершите уроки-зависимости
  2. С разборомLeetCode · внешняя задачаСложность LeetCode: MediumУровень AlgoDS: Основной

    Count Good Nodes in Binary Tree

    Передавай вниз максимум на текущем пути: это минимальное состояние, нужное для решения в вершине.

    Сначала завершите уроки-зависимости
  3. Перенос паттернаLeetCode · внешняя задачаСложность LeetCode: EasyУровень AlgoDS: Разминка

    Leaf-Similar Trees

    Зафиксируй порядок обхода листьев и сравнивай именно последовательности, а не множества значений.

    Сначала завершите уроки-зависимости
  4. СамостоятельноLeetCode · внешняя задачаСложность LeetCode: MediumУровень AlgoDS: Основной

    Longest ZigZag Path in a Binary Tree

    Определи два состояния по направлению следующего шага и не смешивай длину в рёбрах с числом вершин.

    Сначала завершите уроки-зависимости
  5. СамостоятельноLeetCode · внешняя задачаСложность LeetCode: MediumУровень AlgoDS: Основной

    Path Sum III

    Перенеси идею префиксных сумм на путь DFS и обязательно откатывай частоту при выходе из вершины.

    Сначала завершите уроки-зависимости
  6. СамостоятельноLeetCode · внешняя задачаСложность LeetCode: MediumУровень AlgoDS: С вызовом

    Lowest Common Ancestor of a Binary Tree

    Интерпретируй непустой возврат как найденную цель или уже найденного общего предка.

    Сначала завершите уроки-зависимости
Все задачи по теме