Подготовка к собеседованию часто сводится к пугающей цифре: «Нужно решить 500 задач на LeetCode». Но зубрить сотни решений неэффективно. Большинство задач на алгоритмических секциях решаются с помощью нескольких базовых паттернов.

Если вы поймёте суть паттерна — не код, а ход мыслей — вы сможете решить задачу, которую видите впервые. Ниже — 6 фундаментальных шаблонов с «алгоритмом мыслей», маркерами (по каким признакам понять, что нужен именно этот паттерн) и примерами классических задач.

1. Arrays + HashMap

Этот паттерн используется, когда нужно избежать вложенных циклов O(n²) и ускорить поиск за счёт дополнительной памяти. Мы жертвуем памятью O(n) ради скорости O(n).

Алгоритм мыслей
  1. 1 Создаю память: Set (для уникальности) или Map (для частот / пар).
  2. 2 Иду по массиву в цикле.
  3. 3 Для текущего элемента задаю вопрос: «Что мне нужно узнать из уже обработанного прошлого?»
  4. 4 Если ответ уже есть в памяти → делаю действие: return / count++ / add result.
  5. 5 Если нет (или после обработки) → записываю текущий элемент в память.
TwoSum.kt
// Найти два числа, дающих в сумме target fun twoSum(nums: IntArray, target: Int): IntArray { val seen = HashMap<Int, Int>() // значение → индекс for ((i, num) in nums.withIndex()) { val complement = target - num if (complement in seen) // ← вопрос к прошлому return intArrayOf(seen[complement]!!, i) seen[num] = i // ← записываю в память } return intArrayOf() }

2. Two Pointers

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

Алгоритм мыслей
  1. 1 Ставлю два указателя: left и right.
  2. 2 Обычно left в начале, right в конце (или оба в начале с разной скоростью).
  3. 3 Смотрю на пару элементов под указателями.
  4. 4 Задаю вопрос: «Какой указатель нужно сдвинуть, чтобы приблизиться к ответу?»
  5. 5 Двигаю нужный указатель.
  6. 6 Повторяю, пока left < right.
💡 Главная мысль: два указателя двигаются навстречу или в одну сторону, чтобы гарантированно найти ответ без вложенного цикла.

3. Sliding Window

Развитие идеи двух указателей. Окно — это подмассив или подстрока, которая «скользит» по данным. Используется для поиска оптимального непрерывного участка.

Алгоритм мыслей
  1. 1 Создаю окно: два указателя left и right.
  2. 2 Двигаю right и расширяю окно.
  3. 3 Добавляю текущий элемент в состояние окна: sum / count / map / set.
  4. 4 Проверяю: «Окно всё ещё валидно?»
  5. 5 Если невалидно → двигаю left, удаляю элементы из состояния, пока окно снова не станет валидным.
  6. 6 После каждого валидного окна обновляю ответ: max / min / count.
💡 Главная мысль: окно растёт вправо, а слева сжимается, когда нарушается условие.

Паттерны — это база. Но на собесе нужна система.

На менторских сессиях мы разбираем эти паттерны на реальных задачах с собеседований в Яндекс, Авито и Тинькофф — с живым code review и честной обратной связью.

Магия O(log n). Если вы можете отбросить половину вариантов за один шаг — используйте бинарный поиск. Работает не только с отсортированными массивами, но и с любым пространством ответов, где можно проверить true/false.

Алгоритм мыслей
  1. 1 Данные отсортированы или ответ можно проверять через true/false (бинарный поиск по ответу).
  2. 2 Ставлю границы: left и right.
  3. 3 Беру середину: mid = left + (right - left) / 2.
  4. 4 Задаю вопрос: «Ответ левее, правее или это он?»
  5. 5 Если ответ меньше → right = mid - 1. Если больше → left = mid + 1.
  6. 6 Повторяю, пока left <= right.
BinarySearch.kt
fun binarySearch(arr: IntArray, target: Int): Int { var left = 0 var right = arr.size - 1 while (left <= right) { val mid = left + (right - left) / 2 when { arr[mid] == target -> return mid // нашёл arr[mid] < target -> left = mid + 1 // правее else -> right = mid - 1 // левее } } return -1 }
💡 Главная мысль: каждый шаг отбрасывает ровно половину пространства поиска.

5. DFS / BFS

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

Алгоритм мыслей
  1. 1 Понимаю структуру: граф / дерево / сетка (матрица).
  2. 2 Создаю visited, чтобы не ходить по кругу.
  3. 3 Выбираю обход: DFS — вглубь (рекурсия / Stack), BFS — слоями (Queue).
  4. 4 Беру текущую вершину / клетку.
  5. 5 Проверяю: «Это ответ или надо идти к соседям?»
  6. 6 Добавляю соседей, если валидные и не посещены.
  7. 7 Повторяю, пока есть куда идти.
💡 Главная мысль: DFS — для исследования всех вариантов, бэктрекинга. BFS — для кратчайшего пути в невзвешенном графе и обхода по уровням.

6. Stack

Стек (LIFO — Last In, First Out) идеален для задач, где нужно отслеживать парность, вложенность, отмену действий или сравнивать текущий элемент с предыдущими.

Алгоритм мыслей
  1. 1 Создаю стек (в Kotlin это ArrayDeque).
  2. 2 Иду по элементам входных данных.
  3. 3 Для текущего элемента задаю вопрос: «Он закрывает / отменяет / сравнивается с верхушкой стека?»
  4. 4 Если да → делаю pop (или серию pop).
  5. 5 Если нет → делаю push.
  6. 6 В конце проверяю пустоту стека или собираю результат.
ValidParentheses.kt
fun isValid(s: String): Boolean { val stack = ArrayDeque<Char>() val pairs = mapOf(')' to '(', ']' to '[', '}' to '{') for (ch in s) { if (ch in pairs) { if (stack.isEmpty() || stack.removeLast() != pairs[ch]) return false // ← закрывает, но не ту } else { stack.addLast(ch) // ← открывающая → push } } return stack.isEmpty() }

Как с этим работать на собеседовании

Не пытайтесь сразу написать идеальный код. На собеседовании сначала вслух проговорите алгоритм мыслей из этой шпаргалки:

1

Прочитайте условие и найдите маркер: «подстрока» → Sliding Window, «отсортирован» → Binary Search

2

Назовите паттерн вслух интервьюеру: «Я вижу здесь задачу на два указателя, потому что…»

3

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

4

Напишите код, комментируя каждую часть: «тут я расширяю окно, тут сжимаю…»

Алгоритмические паттерны — это мышечная память. Чем чаще вы применяете эти 6 подходов на практике, тем быстрее будете «щелкать» задачи на лайв-кодинге. Но если вы чувствуете, что паттерны знаете, а на собеседовании всё равно теряетесь — скорее всего, проблема не в знаниях, а в отсутствии системы и тренировки подачи.