Усложнение паттерна "снизу вверх"

Раньше в задачах выше ответ совпадал с тем, что мы возвращали родителю. Но так бывает не всегда — иногда ответ задачи и возвращаемое значение это разные вещи по смыслу.

Простой случай — высота дерева

Высота дерева — число узлов в самой длинной ветке. Здесь всё просто: возвращаем максимум из левого и правого + 1. Ответ задачи = возвращаемое значение.

def max_depth(node):
    if node is None:
        return 0

    left  = max_depth(node.left)
    right = max_depth(node.right)

    return max(left, right) + 1
Усложнение — диаметр дерева

Дан корень бинарного дерева. Нужно найти диаметр — длину самого длинного пути (в рёбрах) между любыми двумя узлами. Путь не обязательно проходит через корень.

Диаметр через узел = высота левого поддерева + высота правого поддерева. Но родителю мы возвращаем не диаметр, а высоту — ответ и возвращаемое значение разные по смыслу. Поэтому диаметр обновляется отдельно через замыкание, а наверх идёт только высота.

public class Solution
{
    private static int diameter;

    public static int FindDiameter(TreeNode root)
    {
        diameter = 0;
        Dfs(root);
        return diameter;
    }

    private static int Dfs(TreeNode node)
    {
        if (node == null)
        {
            return 0;
        }

        int left = Dfs(node.left);
        int right = Dfs(node.right);

        // Обновляем максимальный диаметр
        diameter = Math.Max(diameter, left + right);

        return Math.Max(left, right) + 1;
    }
}

Оценка сложности

  • Время: O(n), где n — число вершин в дереве, потому что заходим в каждый узел один раз
  • Память: O(h), где h — высота дерева, память тратится на стек рекурсивных вызовов

В более сложных задачах удобно использовать замыкание/ссылку на переменную, чтобы иметь возможность обновлять ответ в каждом отдельном узле и иметь возможность разделить по смыслу ответ и возвращаемое значение.