Структуры данных — это основа алгоритмов. Если вы знаете, что такое стек, очередь и хэш-таблица, и понимаете, когда какую использовать — большинство задач на собеседованиях становится в разы проще.
Разбираем 7 ключевых структур данных на JavaScript: от массивов до деревьев. С реализацией, сложностью операций и задачами для практики.
Массив — самая базовая структура данных. В JavaScript массив — это объект с числовыми ключами и специальными методами.
| Операция | Сложность |
|---|---|
| Доступ по индексу | O(1) |
| Добавление в конец | O(1)* |
| Добавление в начало | O(n) |
| Удаление из начала | O(n) |
| Поиск | O(n) |
*Амортизированная сложность — из-за возможного перераспределения памяти.
const arr = [3, 1, 4, 1, 5, 9, 2, 6];
// Сортировка
arr.sort((a, b) => a - b); // [1, 1, 2, 3, 4, 5, 6, 9]
// Разворот
arr.reverse(); // [9, 6, 5, 4, 3, 2, 1, 1]
// Поиск
arr.indexOf(5); // 2
arr.includes(9); // true
arr.find((x) => x > 4); // 9
// Преобразование
const doubled = arr.map((x) => x * 2);
const evens = arr.filter((x) => x % 2 === 0);
const sum = arr.reduce((acc, x) => acc + x, 0);
// Срез и вставка
arr.slice(1, 4); // [6, 5, 4] — без изменения оригинала
arr.splice(2, 1, 10); // удалить 1 элемент начиная с позиции 2, вставить 10Дан отсортированный массив и число target. Найдите два элемента, сумма которых равна target.
function twoSumSorted(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) return [left, right];
if (sum < target) left++;
else right--;
}
return [-1, -1];
}
twoSumSorted([2, 7, 11, 15], 9); // [0, 1]Поверните матрицу n x n по часовой стрелке.
function rotate(matrix) {
const n = matrix.length;
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
}
}
for (let row of matrix) {
row.reverse();
}
return matrix;
}
rotate([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
// [[7, 4, 1], [8, 5, 2], [9, 6, 3]]Стек — структура данных «последним пришёл — первым вышел» (LIFO). Как стопка тарелок: кладёте сверху, снимаете сверху.
| Операция | Сложность |
|---|---|
| push (добавить) | O(1) |
| pop (удалить) | O(1) |
| peek (посмотреть верхний) | O(1) |
| isEmpty | O(1) |
const stack = [];
stack.push(1); // [1]
stack.push(2); // [1, 2]
stack.push(3); // [1, 2, 3]
stack.pop(); // 3, стек: [1, 2]
stack.pop(); // 2, стек: [1]
stack[stack.length - 1]; // 1 (peek)Массив в JavaScript идеально подходит для стека — push и pop работают за O(1).
Данная строка содержит ()[]{}. Проверьте, что скобки расставлены правильно.
function isValid(s) {
const stack = [];
const pairs = { ")": "(", "]": "[", "}": "{" };
for (const char of s) {
if (char === "(" || char === "[" || char === "{") {
stack.push(char);
} else {
if (stack.pop() !== pairs[char]) return false;
}
}
return stack.length === 0;
}
isValid("()[]{}"); // true
isValid("(]"); // false
isValid("([)]"); // false
isValid("{[()]}"); // trueВычислите значение арифметического выражения в постфиксной записи (обратная польская нотация).
function evalRPN(tokens) {
const stack = [];
for (const token of tokens) {
if ("+-*/".includes(token)) {
const b = stack.pop();
const a = stack.pop();
switch (token) {
case "+": stack.push(a + b); break;
case "-": stack.push(a - b); break;
case "*": stack.push(a * b); break;
case "/": stack.push(Math.trunc(a / b)); break;
}
} else {
stack.push(Number(token));
}
}
return stack[0];
}
evalRPN(["2", "1", "+", "3", "*"]); // 9 → ((2 + 1) * 3)
evalRPN(["4", "13", "5", "/", "+"]); // 6 → (4 + (13 / 5))Очередь — структура данных «первым пришёл — первым вышел» (FIFO). Как очередь в магазине: кто встал раньше, того обслужат раньше.
| Операция | Сложность |
|---|---|
| enqueue (добавить в конец) | O(1) |
| dequeue (удалить из начала) | O(n)* |
| peek (посмотреть первый) | O(1) |
| isEmpty | O(1) |
*Для массива — O(n), потому что нужно сдвинуть все элементы. Для связного списка — O(1).
const queue = [];
queue.push(1); // [1]
queue.push(2); // [1, 2]
queue.push(3); // [1, 2, 3]
queue.shift(); // 1, очередь: [2, 3]
queue.shift(); // 2, очередь: [3]Для каждого элемента массива найдите ближайший больший элемент справа. Если такого нет — -1.
function nextGreaterElement(nums) {
const result = new Array(nums.length).fill(-1);
const stack = [];
for (let i = nums.length - 1; i >= 0; i--) {
while (stack.length && stack[stack.length - 1] <= nums[i]) {
stack.pop();
}
result[i] = stack.length ? stack[stack.length - 1] : -1;
stack.push(nums[i]);
}
return result;
}
nextGreaterElement([4, 5, 2, 25]); // [5, 25, 25, -1]
nextGreaterElement([13, 7, 6, 12]); // [-1, 12, 12, -1]Связный список — цепочка узлов, где каждый хранит значение и ссылку на следующий. В отличие от массива, элементы не хранятся подряд в памяти.
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
// Создание списка: 1 → 2 → 3
const head = new ListNode(1,
new ListNode(2,
new ListNode(3)
)
);
// Обход
function traverse(head) {
let current = head;
while (current) {
console.log(current.val);
current = current.next;
}
}
// Вставка в начало
function prepend(head, val) {
return new ListNode(val, head);
}
// Удаление узла по значению
function remove(head, val) {
if (!head) return null;
if (head.val === val) return head.next;
head.next = remove(head.next, val);
return head;
}| Операция | Сложность |
|---|---|
| Доступ по индексу | O(n) |
| Добавление в начало | O(1) |
| Удаление из начала | O(1) |
| Поиск | O(n) |
Разворачивает односвязный список.
function reverseList(head) {
let prev = null;
let current = head;
while (current) {
const next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;
}Проверьте, есть ли в связном списте цикл (алгоритм Флойда — «черепаха и заяц»).
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true;
}
return false;
}Хэш-таблица хранит пары ключ-значение. В JavaScript это Map и обычные объекты.
| Операция | Сложность (среднее) | Сложность (худший) |
|---|---|---|
| Добавление | O(1) | O(n) |
| Удаление | O(1) | O(n) |
| Поиск | O(1) | O(n) |
// Object — ключи только строки/символы
const obj = {};
obj["key"] = 1;
obj[123] = 2; // ключ становится "123"
// Map — любые ключи
const map = new Map();
map.set("key", 1);
map.set(123, 2); // ключ — число 123
map.set(true, 3); // ключ — булево значение
map.get(123); // 2
map.size; // 3Найдите пересечение двух массивов (уникальные элементы).
function intersection(nums1, nums2) {
const set = new Set(nums1);
const result = [];
for (const num of nums2) {
if (set.has(num)) {
result.push(num);
set.delete(num);
}
}
return result;
}
intersection([1, 2, 2, 1], [2, 2]); // [2]
intersection([4, 9, 5], [9, 4, 9, 8, 4]); // [9, 4]Найдите длину самой длинной подстроки без повторяющихся символов.
function lengthOfLongestSubstring(s) {
const map = new Map();
let maxLen = 0;
let left = 0;
for (let right = 0; right < s.length; right++) {
if (map.has(s[right]) && map.get(s[right]) >= left) {
left = map.get(s[right]) + 1;
}
map.set(s[right], right);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
lengthOfLongestSubstring("abcabcbb"); // 3
lengthOfLongestSubstring("bbbbb"); // 1Множество хранит только уникальные значения. Аналог Set в математике.
| Операция | Сложность |
|---|---|
| Добавление | O(1) |
| Удаление | O(1) |
| Проверка наличия | O(1) |
| Размер | O(1) |
const set = new Set([1, 2, 3, 2, 1]);
set.size; // 3
set.add(4);
set.delete(1);
set.has(2); // true
// Быстрое удаление дубликатов из массива
const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)]; // [1, 2, 3]
// Множественные операции
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
// Пересечение
const intersectionSet = new Set([...a].filter((x) => b.has(x))); // {2, 3}
// Объединение
const union = new Set([...a, ...b]); // {1, 2, 3, 4}
// Разность
const diff = new Set([...a].filter((x) => !b.has(x))); // {1}Проверьте, можно ли составить палиндром из строки (переставив символы).
function canPermutePalindrome(s) {
const charCount = new Map();
for (const char of s) {
charCount.set(char, (charCount.get(char) || 0) + 1);
}
let oddCount = 0;
for (const count of charCount.values()) {
if (count % 2 !== 0) oddCount++;
if (oddCount > 1) return false;
}
return true;
}
canPermutePalindrome("code"); // false
canPermutePalindrome("carerac"); // trueДерево — структура данных с узлами, каждый из которых имеет значение и ссылки на дочерние узлы. Бинарное дерево — каждый узел имеет не более двух детей.
Для каждого узла: левое поддерево < узел < правое поддерево. Позволяет искать за O(log n).
class TreeNode {
constructor(val, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
// 4
// / \
// 2 6
// / \ / \
// 1 3 5 7
const root = new TreeNode(4,
new TreeNode(2,
new TreeNode(1),
new TreeNode(3)
),
new TreeNode(6,
new TreeNode(5),
new TreeNode(7)
)
);// In-order (левый → корень → правый) — отсортированный порядок
function inorder(root) {
if (!root) return [];
return [
...inorder(root.left),
root.val,
...inorder(root.right),
];
}
// Pre-order (корень → левый → правый) — для копирования дерева
function preorder(root) {
if (!root) return [];
return [
root.val,
...preorder(root.left),
...preorder(root.right),
];
}
// Post-order (левый → правый → корень) — для удаления дерева
function postorder(root) {
if (!root) return [];
return [
...postorder(root.left),
...postorder(root.right),
root.val,
];
}
// BFS (по уровням)
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length) {
const level = [];
const levelSize = queue.length;
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
level.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(level);
}
return result;
}
inorder(root); // [1, 2, 3, 4, 5, 6, 7]
preorder(root); // [4, 2, 1, 3, 6, 5, 7]
levelOrder(root); // [[4], [2, 6], [1, 3, 5, 7]]| Операция | Сложность (сбалансированное) | Сложность (вырожденное) |
|---|---|---|
| Поиск | O(log n) | O(n) |
| Вставка | O(log n) | O(n) |
| Удаление | O(log n) | O(n) |
Найдите максимальную глубину бинарного дерева.
function maxDepth(root) {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
maxDepth(root); // 3Проверьте, является ли дерево зеркальным относительно своей оси.
function isSymmetric(root) {
function isMirror(t1, t2) {
if (!t1 && !t2) return true;
if (!t1 || !t2) return false;
return (
t1.val === t2.val &&
isMirror(t1.left, t2.right) &&
isMirror(t1.right, t2.left)
);
}
return isMirror(root.left, root.right);
}| Задача | Структура |
|---|---|
| Доступ по индексу | Массив |
| LIFO (последний элемент) | Стек |
| FIFO (первый элемент) | Очередь |
| Быстрый поиск по ключу | Map / Set |
| Вставка в середину | Связный список |
| Отсортированные данные + бинарный поиск | BST / отсортированный массив |
| Граф / дерево | Связный список + Map |
Эти структуры данных — фундамент для любых алгоритмов. Знать их теорию — хорошо. Уметь реализовать и использовать на практике — лучше.
Попробуйте решить задачи на Девстанции — там есть десятки задач, которые тренируют работу с массивами, стеками, очередями и деревьями. AI проверит ваш код и подскажет, если есть более эффективное решение.
А если готовитесь к собеседованию — посмотрите паттерны решения задач, чеклист из 50 вопросов и план подготовки за неделю.