Рассмотрим 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 пайплайны)
- Что нужно пройти, прежде чем учить следующий предмет
Как использовать:
Алгоритм работает через подсчет входящих ребер.
- Считаем входящие ребра для каждой вершины
- Добавляем все вершины с in-degree = 0 (ни от кого не зависят) в очередь
- Взять вершину из очереди и добавить в результат
- Для всех соседей n уменьшить in-degree[n] на 1
- Если in-degree[n] == 0 — добавить в очередь
- Если результат содержит все вершины, граф без цикла и топологическая сортировка успешна
- Если нет — в графе есть цикл
Задачи с LeetCode:
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 (Вершина → Лево → Право):
-
Начни с корневого узла.
-
Посети текущий узел (например, выведи его value).
-
Рекурсивно обойди левого потомка.
-
Рекурсивно обойди правого потомка.
Можно еще обходить по алгоритму:
- 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()).
-
Заводим min-heap размером K
-
Проходим по массиву:
- Добавляем элемент в кучу
- Если размер кучи > K → удаляем наименьший
- В куче остаются 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
Как использовать:
-
Заводим два указателя — start и end.
-
Двигаем end, расширяя окно.
-
Если окно “не подходит условию” → двигаем start, сужая его.
-
Во время движения сохраняем нужные данные (максимум, сумма, длина и т.д.)
Задачи с 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
Итоги:
Если тебе нужно двигать окно по массиву - этот паттерн 🔥. Универсален в задачах на анализ подотрезков и оптимизацию сканирования.