Рассмотрим 6 ключевых паттернов, которые помогают решать 70% самых популярных задач на LeetCode.

🚀 Прокачаешься в собеседованиях и алгоритмах, даже если раньше было страшно

Два указателя

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

Когда использовать:

  • Найти пару с суммой = target в отсортированном массиве
  • Удалить дубликаты на месте
  • Перевернуть массив/строку
  • Проверить палиндром
  • Слить два отсортированных массива

Как использовать:

let left = 0;
let right = arr.length - 1;

while (left < right) {
// выполняй логику
if (условие) left++;
else right–;
}

Можно использовать left, right, slow, fast — смотря по задаче. Главное — два указателя и движение по массиву/строке.

Задачи с LeetCode:

Задача: 167. Two Sum II - Input array is sorted

// Решение с двумя указателями  
function twoSum(numbers: number[], target: number): number[] {
    let left = 0;
    let right = numbers.length - 1;
    while (left < right) {
        if (numbers[left] + numbers[right] === target) {
            return [left + 1, right + 1]
        }
        if (
            numbers[left] + numbers[right] > target
        ) {
            right--
        } else {
            left++
        }
    }
    return []
};

Задача: 26. Remove Duplicates from Sorted Array

Задача: 344. Reverse String

Задача: 125. Valid Palindrome

Задача: 88. Merge Sorted Array

Итоги:

Two Pointers - универсальный паттерн. Если работаешь с отсортированными массивами или нужна эффективная проверка пары/порядка/зеркальности - 90% вероятность, что он тебе подойдёт.

Обход дерева в ширину

Обходим дерево уровень за уровнем. Используем очередь (queue) для хранения узлов, которые надо обработать.

Когда использовать:

  • Вернуть элементы по уровням
  • Найти минимальную/максимальную глубину дерева
  • Вывести правый/левый вид дерева
  • Найти путь к ближайшему элементу (минимальный путь)

Как использовать:

const queue = [root];
while (queue.length > 0) {
    const node = queue.shift();
// обработка node.val

    if (node.left) queue.push(node.left);
    if (node.right) queue.push(node.right);
}

Задачи с LeetCode:

Задача: 102. Binary Tree Level Order Traversal

function levelOrder(root: TreeNode | null): number[][] {
    if (!root) return [];

    let res: number[][] = [];
    let q: TreeNode[] = [root];
    let levelVals: number[] = [];
    let levelLength = q.length;
    let i = 0;

    while (q.length) {
        let node: TreeNode = q.shift();
        levelVals.push(node.val);
        i++;
        if (node.left) q.push(node.left);
        if (node.right) q.push(node.right);

        if (i === levelLength) {
            res.push(levelVals);
            levelVals = [];
            i = 0;
            levelLength = q.length;
        }
    }

    return res;
};

Задача: 111. Minimum Depth of Binary Tree

Задача: 199. Binary Tree Right Side View

Итоги:

Если задача про расстояния, глубину, обход по уровням или визуальный вид дерева - используй этот паттерн. Это мощный паттерн для деревьев и графов, когда важно порядок посещения.

Топологическая сортировка

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

Топологический порядок:
👉 A, B, C, D или A, C, B, D

Топологическая сортировка используется только с графами без циклов (называются DAG — Directed Acyclic Graph)

Пример не подходящего графа с циклом ниже: A → B → C → A → B → C …

Когда использовать:

  • Разрешить зависимости (запуск задач, установка пакетов)
  • Порядок вычислений переменных и функций
  • Что делать раньше, а что позже
  • Как запускать шаги, если один зависит от предыдущего (CI/CD пайплайны)
  • Что нужно пройти, прежде чем учить следующий предмет

Как использовать:

Алгоритм работает через подсчет входящих ребер.

  1. Считаем входящие ребра для каждой вершины
  2. Добавляем все вершины с in-degree = 0 (ни от кого не зависят) в очередь
  3. Взять вершину из очереди и добавить в результат
  4. Для всех соседей n уменьшить in-degree[n] на 1
  5. Если in-degree[n] == 0 — добавить в очередь
  6. Если результат содержит все вершины, граф без цикла и топологическая сортировка успешна
  7. Если нет — в графе есть цикл

Задачи с LeetCode:

Задача: 207. Course Schedule

function findOrder(numCourses: number, prerequisites: number[][]): number[] {
    const inDegrees = new Array(numCourses).fill(0);
    for (const [v] of prerequisites) inDegrees[v]++

    const queue: number[] = [];
    for (let i = 0; i < inDegrees.length; i++) {
        inDegrees[i] === 0 && queue.push(i);
    }

    const topologicalSortedOrder: number[] = [];
    while (queue.length) {
        const node: number = queue.shift();
        numCourses -= 1;
        topologicalSortedOrder.push(node);
        for (const [v, e] of prerequisites) {
            if (e === node) {
                inDegrees[v] -= 1;
                inDegrees[v] === 0 && queue.push(v);
            }
        }
    }

    if (numCourses > 0) return []; // the cycle is detected  

    return topologicalSortedOrder;
}

Задача: 210. Course Schedule II

Итоги:

Topological Sort — топовый инструмент для задач, где есть зависимости, приоритеты, очередность. Работает только с графами без циклов. Если есть цикл — всё, не наш случай. Смотрим на паттерн ниже - Обход дерева в глубину.

Обход дерева в глубину

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

Идём от корня и всегда идем вглубь, пока можно, а потом возвращаешься.

A → B → D → E → C

Когда использовать:

  • Поиск в глубину в графах и деревьях
  • Проверка, есть ли путь от A до B
  • Рекурсивные алгоритмы (например, подсчет всех путей или комбинаций)

Как использовать:

Алгоритм Pre-order (Вершина → Лево → Право):

  1. Начни с корневого узла.

  2. Посети текущий узел (например, выведи его value).

  3. Рекурсивно обойди левого потомка.

  4. Рекурсивно обойди правого потомка.

Можно еще обходить по алгоритму:

  • In-order (Лево → Вершина → Право)
  • Post-order (Лево → Право → Вершина)

Задачи с LeetCode:

Задача: 104. Maximum Depth of Binary Tree

function maxDepth(root: TreeNode | null): number {
    if (!root) return 0;
    let maxLeft = maxDepth(root.left);
    let maxRight = maxDepth(root.right);
    return Math.max(maxLeft, maxRight) + 1;
};

Задача: 297. Serialize and Deserialize Binary Tree

Задача: 112. Path Sum

Итоги:

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

Топ K элементов

Используем когда нужно найти K самых больших или K самых маленьких элементов в массиве или потоке данных, один из самых частых паттернов используемый в задачах на Leet Code

Когда использовать:

  • Найти Top K наибольших/наименьших элементов
  • Найти K самых частых элементов (слов, чисел, символов)

Как использовать:

В JavaScript/TypeScript нет нативного heap, но можно использовать PriorityQueue в других языках или реализовать руками (или костылить через .sort()).

  1. Заводим min-heap размером K

  2. Проходим по массиву:

  • Добавляем элемент в кучу
  • Если размер кучи > K → удаляем наименьший
  1. В куче остаются K наибольших элементов

Задачи с LeetCode:

Задача: 347. Top K Frequent Elements

function topKFrequent(nums: number[], k: number): number[] {
    const heap = new MaxPriorityQueue({priority: comparator});
    const map = new Map<number, number>();

    // add frequencies to map  
    for (const num of nums) {
        if (!map.has(num))
            map.set(num, 0);
        map.set(num, map.get(num) + 1);
    }

    // fill max heap  
    for (const [num, frequency] of map)
        heap.enqueue([num, frequency]);

    const ans = [];
    for (let i = 0; i < k; i++) {
        const [num] = heap.dequeue().element;
        ans.push(num);
    }
    return ans;
};

function comparator([, frequency]: [number, number]) {
    return frequency
}

Задача: 692. Top K Frequent Words

Задача: 215. Kth Largest Element in an Array

Итоги:

Если нужно держать приоритет или отбирать лучшие K - Min/Max Heap тебе в помощь.
Работает в задачах про частоты, приоритеты, топы и ранжирование.

Скользящее окно

Это когда ты обрабатываешь подмассив фиксированной или переменной длины, двигаясь по массиву, но не пересчитываешь всё каждый раз, а умно сдвигаешь границы окна.

Когда использовать:

  • Найти максимальную/среднюю сумму в подотрезке
  • Найти подстроку с уникальными символами
  • Проверить, есть ли подотрезок с суммой target

Как использовать:

  1. Заводим два указателя — start и end.

  2. Двигаем end, расширяя окно.

  3. Если окно “не подходит условию” → двигаем start, сужая его.

  4. Во время движения сохраняем нужные данные (максимум, сумма, длина и т.д.)

Задачи с LeetCode:

Задача: 643. Maximum Average Subarray I

Задача: 3. Longest Substring Without Repeating Characters

function lengthOfLongestSubstring(s: string): number {
    const last = new Array<number>(128).fill(-1)
    let start = 0
    let maxLen = 0

    for (let i = 0; i < s.length; i++) {
        const code = s.charCodeAt(i)
        if (last[code] >= start) {
            start = last[code] + 1
        }
        last[code] = i
        maxLen = Math.max(maxLen, i - start + 1)
    }

    return maxLen
}

Задача: 209. Minimum Size Subarray Sum

Итоги:

Если тебе нужно двигать окно по массиву - этот паттерн 🔥. Универсален в задачах на анализ подотрезков и оптимизацию сканирования.