{}const=>[]async()letfn</>var
JSАлгоритмы

Алгоритмические задачи на JavaScript: 12 упражнений по шагам

Практикуем массивы, строки, Map, Set, два указателя, стек, очередь, сортировку, бинарный поиск и оценку сложности. Разбор задачи о паре с заданной суммой.

К

Кодик

Автор

7 мин чтения

Алгоритмические задачи на JavaScript полезно решать по одному циклу: уточнить вход и выход, придумать простой вариант, проверить крайние случаи, оценить сложность и только потом оптимизировать. Начните с массивов, строк, Map и Set, затем добавьте два указателя, стек, очередь, сортировку и бинарный поиск.

Задача не проверяет способность вспомнить красивый трюк. Она проверяет моделирование данных и аргументацию. В JavaScript массив является универсальной индексируемой коллекцией, Map хранит пары ключ-значение, Set поддерживает уникальность. Уточняйте, можно ли менять исходный массив, нужны ли индексы или значения, допустимы ли дубли и как обрабатывать отсутствие ответа.

1Сначала пример

Пройдите алгоритм руками на пяти элементах.

2Потом код

Разделите хранение состояния и обход.

3После тест

Пустой ввод, дубли, отрицательные и нет решения.

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

Алгоритмические задачи на JavaScript: пара с заданной суммой

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

function findPair(numbers, target) {
  const seen = new Map();

  for (let index = 0; index < numbers.length; index += 1) {
    const value = numbers[index];
    const needed = target - value;

    if (seen.has(needed)) {
      return [seen.get(needed), index];
    }

    // Сохраняем индекс уже просмотренного значения
    seen.set(value, index);
  }

  return null;
}

console.log(findPair([2, 7, 11, 15], 9)); // [0, 1]
Ожидаемый результат
  • Для [2, 7, 11, 15] и 9 функция возвращает [0, 1]
  • Один проход использует дополнительную память Map

Время работы в среднем линейно по числу элементов при обычном поведении Map, память тоже линейна в худшем случае. Проверьте [3, 3] с target 6: первый индекс уже лежит в Map, когда обход доходит до второго элемента. Проверьте пустой массив и отсутствие ответа. Если вход отсортирован и нужны значения, можно использовать два указателя без Map, но контракт должен позволять потерю исходных индексов или их отдельное сохранение.

🔥 100 000+ учеников уже с нами

Устал читать теорию?
Пора кодить!

Кодик — приложение, где ты учишься программировать через практику. AI-наставник, интерактивные уроки, реальные проекты.

🤖 AI 24/7
🎓 Сертификаты
💰 Бесплатно
🚀 Начать учиться
Присоединились сегодня

12 задач в порядке роста

ЭлементЧто означаетЧто делать
МассивыМаксимум, разворот, частотыЦикл и accumulator
СтрокиПалиндром, скобки, анаграммаНормализация и стек
Map и SetДубли, две суммы, пересечениеБыстрый поиск
УказателиОтсортированные пары, окноИнвариант границ
ПоискБинарный поиск и ответСокращение диапазона

Для каждой задачи храните три версии: постановка, собственное решение и тесты. Не оптимизируйте до рабочего простого варианта. Сортировка может упростить алгоритм, но стоит O(n log n) и иногда меняет исходный массив. Метод sort без comparator сортирует значения как строки, поэтому для чисел используйте (a, b) => a - b. Это частый источник неожиданных результатов.

Когда брать массив, Map, Set или два указателя
Выбор структуры под задачу: массив для прохода и накопления, Map и Set для дублей и поиска дополнения target - value, два указателя для отсортированного входа.

Практика: соберите two sum на Map и проверьте крайние случаи

Создайте файл findPair.js и перенесите в него функцию из статьи вместе с пятью вызовами console.log. Успехом считайте такой прогон: findPair([2, 7, 11, 15], 9) возвращает [0, 1], а вход без ответа возвращает null. Задача закрыта, когда вы можете вслух назвать число проходов по массиву и объём памяти под Map.

  1. Контракт two sum. Запишите комментарием в начале файла: на входе массив чисел и target, на выходе два индекса или null. Там же отметьте, допустимы ли дубли и разрешено ли менять исходный массив.
  2. Наивный двойной цикл. Напишите findPairSlow с двумя вложенными циклами и счётчиком сравнений. На массиве из 1000 случайных чисел счётчик покажет сотни тысяч сравнений, и это ваша точка отсчёта.
  3. Версия на Map. Перепишите функцию через new Map() и проверку seen.has(target - value), сохраняя индекс после проверки. Выведите оба ответа рядом: значения совпадают, а счётчик падает до длины массива.
  4. Тест на дубли. Вызовите findPair([3, 3], 6) и убедитесь, что вернулись индексы [0, 1], а не один и тот же элемент. Это и есть доказательство, что seen.set идёт после seen.has.
  5. Границы и sort. Прогоните пустой массив, массив из одного элемента, отрицательные числа и вход без пары: везде должен вернуться null. Отдельно отсортируйте [10, 9, 100] через sort() и через sort((a, b) => a - b) и сравните два вывода.

Переставьте seen.set(value, index) выше проверки seen.has(needed) и снова запустите findPair([3, 3], 6). Функция вернёт [0, 0], то есть сложит элемент сам с собой: значение попадает в Map раньше, чем ищется дополнение. Второй излом сделайте на сортировке: вызовите [10, 9, 100].sort() без comparator и посмотрите на [10, 100, 9], где 100 стоит перед 9 из-за сравнения строк. Верните обе строки как было и убедитесь, что [0, 1] и числовой порядок вернулись.

Критерий готовности. Вы можете объяснить, что лежит в Map, почему ищется именно target - value и откуда берётся линейное время. Подтверждение простое: findPair([2, 7, 11, 15], 9) даёт [0, 1], findPair([3, 3], 6) даёт [0, 1], findPair([], 5) даёт null. Отдельно назовите, сколько записей окажется в Map в худшем случае.

Дальше решите ту же пару с заданной суммой двумя указателями на отсортированном массиве: сумма больше target - двигаете правую границу, меньше - левую, дополнительная память не нужна. Сравните контракты: версия с указателями отдаёт значения, а исходные индексы теряются после сортировки. Потом идите по списку из 12 задач в его порядке: частоты через Map, палиндром и скобки через стек, затем бинарный поиск. Для каждой задачи храните три файла: постановку, своё решение и тесты.

Частые ошибки и почему они появляются

Копирование решения после двух минут лишает задачу главной пользы. Вторая ошибка: назвать O(n), не определив n и операции. Третья: проверить только пример из условия. Рабочая подготовка требует написать тесты до просмотра чужого решения и вернуться к задаче через несколько дней без подсказки.

Нет контракта

Уточните формат ответа, дубли, mutability и отсутствие решения.

Скрытая мутация sort

Копируйте массив, если исходный должен сохраниться.

Сложность без объяснения

Посчитайте проходы, lookup и дополнительную память.

Самопроверка

Зачем нужен Map в задаче two sum?

Map в two sum хранит уже просмотренные значения вместе с их индексами. За счёт этого дополнение target - value ищется за один проход, без вложенного цикла. Платой становится память: в худшем случае в Map окажется весь массив.

Почему sort для чисел вызывают с (a, b) => a - b?

Метод sort без comparator приводит элементы к строкам и сравнивает их посимвольно, поэтому [10, 9, 100] превращается в [10, 100, 9]. Comparator (a, b) => a - b задаёт числовое сравнение. Помните и о втором эффекте: sort меняет исходный массив, так что копируйте его, если оригинал нужен дальше.

Когда удобнее решать задачу двумя указателями?

Два указателя удобны, когда вход отсортирован и условие позволяет двигать границы навстречу друг другу. Дополнительная память при этом не тратится, в отличие от решения на Map. Минус в том, что после сортировки исходные индексы теряются, если не сохранить их отдельно.

Что должна возвращать функция, если пары с нужной суммой нет?

Функция возвращает null, когда ни для одного элемента дополнение не нашлось в Map. Этот случай проговаривают в контракте до кода, иначе вызывающая сторона попробует разложить null в массив и упадёт. Пустой массив и массив из одного элемента попадают сюда же.

Как проверить алгоритм на дубликатах?

Дубликаты проверяются входом [3, 3] с target 6: корректная функция вернёт [0, 1], то есть два разных индекса. Если пришло [0, 0], значит запись в Map выполняется раньше проверки и элемент складывается сам с собой. Такой тест стоит держать рядом с основным примером [2, 7, 11, 15].

С каких задач начинать подготовку на JavaScript?

Начинайте с массивов и строк: максимум, разворот, частоты, палиндром, скобки, анаграмма. Дальше подключайте Map и Set для дублей и пересечений, потом два указателя для отсортированных данных и бинарный поиск. В подборке 12 задач, и порядок в ней важнее скорости прохождения.

Что делать дальше

Решите 12 задач, повторите четыре из них через неделю и объясните сложность вслух. Основы языка систематизирует курс JavaScript. Выбор первого языка сравните в статье Python или JavaScript, а формат интервью в разборе вопросов junior.

🎯Хватит откладывать

Понравилась статья?
Пора применять на практике!

В Кодик ты не просто читаешь — ты сразу пишешь код. Теория + практика = реальный скилл.

Мгновенная практика
🧠AI объяснит код
🏆Сертификат

Без регистрации • Без карты