Алгоритмическая сложность и Big O
Зачем это фронтендеру?
На собеседованиях в 2026 году всё чаще спрашивают не только React, но и алгоритмы. Понимание Big O помогает:
- Оценить, «тормозит» ли твой код при росте данных
- Выбрать правильный подход к задаче
- Пройти алгоритмическую секцию собеседования
Фронтендеру не нужно знать графы и динамическое программирование на уровне олимпиад. Но понимать, почему один вариант решения быстрее другого — обязательно.
Что такое Big O?
Big O (нотация «О большое») — способ описать, как растёт время выполнения алгоритма (или потребление памяти) при увеличении объёма входных данных.
n — это размер входных данных. Например, количество элементов в массиве.
Big O описывает худший случай — максимальное количество операций, которое может потребоваться.
Аналогия
Представь, что ты ищешь книгу в библиотеке:
| Ситуация | Сложность |
|---|---|
| Ты знаешь точный номер полки и берёшь книгу | O(1) — одна операция |
| Ты идёшь по полкам подряд, пока не найдёшь | O(n) — в худшем случае обойдёшь все полки |
| Ты делишь зал пополам, потом ещё пополам… | O(log n) — каждый шаг отбрасывает половину |
| Для каждой книги ты сравниваешь её с каждой другой | O(n²) — количество операций растёт квадратично |
Основные типы сложностей
O(1) — Константное время
Одна операция, независимо от размера данных.
const first = arr[0];
const map = new Map();
map.set("key", "value");
map.get("key");
Где встречается в фронтенде: доступ к элементу массива по индексу, чтение/запись в Map/Set, доступ к свойству объекта.
O(log n) — Логарифмическое время
На каждом шаге отбрасывается часть данных (обычно половина).
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
Где встречается: бинарный поиск, Map.has() в некоторых реализациях, работа с отсортированными данными.
Важно: бинарный поиск работает только на отсортированных данных.
O(n) — Линейное время
Один проход по данным. Количество операций растёт пропорционально размеру данных.
function findMax(arr) {
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) max = arr[i];
}
return max;
}
Где встречается: Array.prototype.find(), filter(), map(), reduce(), любой цикл по массиву.
O(n log n) — Линеарифмическое время
Обычно это сортировка + проход по данным.
Где встречается: Array.prototype.sort() в JavaScript (использует Timsort — O(n log n)), merge sort, быстрая сортировка в среднем случае.
O(n²) — Квадратное время
Два вложенных цикла. При n = 1000 — миллион операций.
function hasDuplicate(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
if (i !== j && arr[i] === arr[j]) return true;
}
}
return false;
}
Где встречается: вложенные циклы по одному массиву, наивные алгоритмы сортировки (bubble sort, selection sort).
Это типичный «красный флаг» на собеседовании. Почти всегда можно оптимизировать до
O(n).
O(2ⁿ) — Экспоненциальное время
Количество операций удваивается с каждым новым элементом.
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Где встречается: наивная рекурсия, перебор всех подмножеств. При n > 40 уже заметно тормозит.
Пространственная сложность (Space Complexity)
Помимо времени, оценивают память — сколько дополнительной памяти требует алгоритм.
| Подход | Пространственная сложность |
|---|---|
| Изменение массива «на месте» | O(1) |
| Создание нового массива того же размера | O(n) |
Рекурсия с глубиной n | O(n) (стек вызовов) |
// O(1) по памяти — разворачиваем массив на месте
function reverseInPlace(arr) {
let left = 0, right = arr.length - 1;
while (left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]];
left++;
right--;
}
}
// O(n) по памяти — создаём новый массив
function reverseNew(arr) {
return [...arr].reverse();
}
Шпаргалка
| Сложность | Название | Пример |
|---|---|---|
O(1) | Константное | arr[0], map.get(key) |
O(log n) | Логарифмическое | Бинарный поиск |
O(n) | Линейное | for по массиву, find(), filter() |
O(n log n) | Линеарифмическое | sort(), merge sort |
O(n²) | Квадратное | Два вложенных цикла |
O(2ⁿ) | Экспоненциальное | Наивная рекурсия (Фибоначчи) |
Типичные задачи с собеседований
Задача 1. Two Sum
Дан массив чисел и целевая сумма. Найти индексы двух чисел, дающих в сумме
target.
Плохое решение — O(n²):
function twoSum(nums, target) {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) return [i, j];
}
}
}
Хорошее решение — O(n):
function twoSum(nums, target) {
const seen = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (seen.has(complement)) return [seen.get(complement), i];
seen.set(nums[i], i);
}
}
Суть оптимизации: вместо второго цикла используем Map для хранения уже увиденных значений. Map.has() и Map.get() работают за O(1), поэтому общая сложность — O(n).
Задача 2. Проверка на дубликаты
Есть ли в массиве повторяющиеся элементы?
Плохо — O(n²):
function hasDuplicate(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) return true;
}
}
return false;
}
Хорошо — O(n):
function hasDuplicate(arr) {
return new Set(arr).size !== arr.length;
}
Задача 3. Подсчёт частоты символов
Посчитать, сколько раз каждый символ встречается в строке.
Решение — O(n):
function charFrequency(str) {
const freq = {};
for (const char of str) {
freq[char] = (freq[char] || 0) + 1;
}
return freq;
}
Это O(n) по времени и O(k) по памяти, где k — количество уникальных символов.
Задача 4. Палиндром
Проверить, является ли строка палиндромом.
Решение — O(n):
function isPalindrome(str) {
let left = 0, right = str.length - 1;
while (left < right) {
if (str[left] !== str[right]) return false;
left++;
right--;
}
return true;
}
Два указателя — один из начала, другой из конца. Сходятся к середине. O(n) по времени, O(1) по памяти.
Задача 5. Поиск пропущенного числа
В массиве от 1 до
nпропущено одно число. Найти его.
Решение — O(n):
function findMissing(arr, n) {
const expectedSum = (n * (n + 1)) / 2;
const actualSum = arr.reduce((sum, num) => sum + num, 0);
return expectedSum - actualSum;
}
Используем формулу суммы арифметической прогрессии вместо сортировки или хеш-таблицы.
Как оптимизировать: паттерны
1. Заменить вложенный цикл на хеш-таблицу
// O(n²) → O(n)
const set = new Set(arr);
if (set.has(target - current)) { /* ... */ }
2. Два указателя (для отсортированных массивов)
// O(n²) → O(n)
let left = 0, right = arr.length - 1;
while (left < right) { /* сдвигаем указатели */ }
3. Скользящее окно
// O(n²) → O(n)
let sum = 0;
for (let i = 0; i < k; i++) sum += arr[i];
let max = sum;
for (let i = k; i < arr.length; i++) {
sum += arr[i] - arr[i - k];
max = Math.max(max, sum);
}
4. Мемоизация (для рекурсии)
// O(2ⁿ) → O(n)
const memo = {};
function fib(n) {
if (n <= 1) return n;
if (memo[n] !== undefined) return memo[n];
memo[n] = fib(n - 1) + fib(n - 2);
return memo[n];
}
Что спрашивают на собеседованиях
- «Оцените сложность вашего решения» — всегда думай об этом до того, как начнёшь писать.
- «Можете ли вы сделать это быстрее?» — если написал
O(n²), тебя попросят оптимизировать доO(n)илиO(n log n). - «Какой trade-off между временем и памятью?» — часто можно ускорить алгоритм, пожертвовав памятью (хеш-таблицы, мемоизация).
- «Что будет, если массив содержит миллион элементов?» — проверка, понимаешь ли ты разницу между
O(n)иO(n²)на практике. - Задачи на структуру данных —
Map,Set, стек, очередь. Знать, какие операции за какую сложность выполняются.
Встроенные структуры данных JS и их сложность
| Операция | Array | Map | Set |
|---|---|---|---|
| Доступ по индексу/ключу | O(1) | O(1) | — |
Поиск (includes / has) | O(n) | O(1) | O(1) |
| Вставка в конец | O(1) | O(1) | O(1) |
| Вставка в начало | O(n) | O(1) | O(1) |
| Удаление из середины | O(n) | O(1) | O(1) |
Главное правило: если нужен частый поиск — используй
SetилиMapвместо массива.
Ресурсы для практики
- LeetCode — задачи с фильтрацией по сложности (Easy → Medium → Hard)
- NeetCode 150 — 150 задач, покрывающих все основные паттерны
- Big O Cheat Sheet — визуальная шпаргалка по сложностям