Бесплатная бета: 60 дней полного доступа, без карты.мест осталось: 84Зарегистрироваться бесплатно

Мы используем необходимые куки для работы сайта (вход и язык). Если ты согласишься, мы также загрузим Google Analytics, чтобы видеть, какие страницы читают, и Google reCAPTCHA для защиты форм обратной связи и сообщений об ошибке от спама. Политика конфиденциальности

Все паттерны

DP на деревьях

DP on trees

O(n)

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

Обновлено 17 окт. 2026 г.

DP на деревьях: как это работает?

Решите, что один узел возвращает родителю. Часто это пара, по значению на каждый вариант.

Сначала спуститесь во всех потомков. До их ответа ничего решить нельзя.

Соберите ответ узла из ответов потомков. Эта сборка и есть переход.

Верните результат наверх. Больше родитель о поддереве ничего не знает.

Если ответ может лежать где угодно, ведите глобальный максимум при возврате. Одного корня не всегда хватает.

Один обратный обход касается каждого узла один раз. В этом вся стоимость.

Источники: Википедия: Динамическое программирование, Википедия: Обход дерева

  1. лист возвращает (3, 0)Грабим дома на дереве. Взять лист даёт 3, пропустить даёт 0.
  2. узел 2 возвращает (2, 3)Взять его и пропустить потомка либо пропустить и взять лучшее.
  3. узел 3 возвращает (3, 1)Его собственный потомок стоит всего 1.
  4. корень взят = 3 + 3 + 1Взятие корня заставляет пропустить обоих потомков.
  5. ответ = 7Семёрка бьёт шестёрку, которую даёт пропуск корня.
Наибольшая сумма узлов без пары родитель и потомок
maxNonAdjacent(root) // root = [3, 4, 5, 1, 3, null, 1]
над узлом incl | excl: лучшая сумма, если узел взят | пропущен
node.value = -inclSum = -exclSum = -

Выбираем узлы с самой большой суммой так, чтобы среди выбранных не было родителя и потомка. Каждый узел сообщит два числа: лучшее, если его взять, и лучшее, если пропустить.

DP на деревьях: шаблон кода

function maxNonAdjacent(node) {
    if (!node) return [0, 0]; // [incl, excl]
    let inclSum = node.value;
    let exclSum = 0;
    for (const child of node.children) {
        const [inclC, exclC] = maxNonAdjacent(child);
        inclSum += exclC;
        exclSum += Math.max(inclC, exclC);
    }
    return [inclSum, exclSum];
}

DP на деревьях: разбор примера

Наибольшая сумма по любому пути

Верните наибольшую сумму по любому пути в двоичном дереве.

Путь может начинаться и кончаться где угодно, но не может ветвиться дважды.

Каждый узел возвращает лучший прямой путь, идущий вниз через него.

Отрицательная ветка отбрасывается. Лучший разветвлённый путь пишется в глобальный максимум при возврате.

function maxPathSum(root) {
    let best = -Infinity;

    function down(node) {
        if (!node) return 0;

        // a negative branch is worse than taking nothing at all
        const left = Math.max(0, down(node.left));
        const right = Math.max(0, down(node.right));

        best = Math.max(best, node.val + left + right); // the fork stays local

        return node.val + Math.max(left, right); // the parent can only use one side
    }

    down(root);
    return best;
}

DP на деревьях: когда применять?

Паттерн «DP на деревьях» нужен, когда в условии есть слова «лучший выбор по иерархии или оргструктуре», «нельзя выбрать узел вместе с его прямым родителем» или «постпорядок: дети решаются раньше родителя». Решение на нём обычно укладывается в O(n).

  • лучший выбор по иерархии или оргструктуре
  • нельзя выбрать узел вместе с его прямым родителем
  • постпорядок: дети решаются раньше родителя
  • каждый узел объединяет состояния своих детей

Слова в условии для всех паттернов собраны в одной таблице: шпаргалка по паттернам алгоритмов.

DP на деревьях: с чем путают?

Паттерн «DP на деревьях» чаще всего путают с этими паттернами: Обход дерева, Динамическое программирование (1-D), Рекурсия с мемоизацией и Обход графа BFS / DFS.

  • Обход дерева (Tree traversal): Та страница про порядок посещения. Эта про то, что уносится наверх.
  • Динамическое программирование (1-D) (Dynamic programming (1-D)): У прямой одно направление, поэтому хватает индекса. Здесь порядок задаёт структура дерева.
  • Рекурсия с мемоизацией (Recursion with memoization): В дереве подзадачи не повторяются, поэтому кеш не нужен. Каждый узел посещается один раз.
  • Обход графа BFS / DFS (Graph BFS / DFS): В обычный граф можно попасть в узел дважды, и рекурсия может не кончиться. В дереве нельзя.

DP на деревьях: частые ошибки

  • Возвращают то же, что и записывают

    Наверх уходит прямой путь, а лучший ответ может ветвиться. Держите их раздельно.

  • Считают до ответа потомков

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

  • Не разбирают случай пустого потомка

    Узел с одним потомком всё равно вызывает вторую сторону. Верните там нейтральное значение.

  • Начинают максимум с нуля

    Старт с нуля прячет дерево, где все значения отрицательные. Начинайте с минус бесконечности.

Паттерн «DP на деревьях» решает такие задачи с собеседований: Ограбление домов III, Максимальная сумма пути в дереве, Диаметр двоичного дерева, Самый длинный путь из равных значений, Распределение монет в дереве, Число хороших узлов и Наклон дерева.

  • Ограбление домов III: Два состояния на узел: взят или пропущен.
  • Максимальная сумма пути в дереве: Возвращается прямой путь, записывается разветвлённый.
  • Диаметр двоичного дерева: Глубина слева плюс глубина справа в каждом узле.
  • Самый длинный путь из равных значений: Продолжать можно только через равных потомков.
  • Распределение монет в дереве: Излишек передаётся наверх родителю.
  • Число хороших узлов: Максимум несут вниз, а не наверх.
  • Наклон дерева: Разность сумм двух поддеревьев.

DP на деревьях: сложность по времени и памяти

У паттерна «DP на деревьях» целевая сложность O(n).

n узлов дают O(n), если в узле работы на O(1). Несколько состояний на узел это умножают.

Где этот паттерн стоит в 150 шагах