Сложить два числа-гиганта: как я прошел квест на собеседовании в Бигтех
Сложить два числа-гиганта: как я прошел квест на собеседовании в Бигтех
Сложить два числа-гиганта: как я прошел квест на собеседовании в Бигтех
Привет, Хабр! Меня зовут Евгений Жуков, я работаю в Битриксе мобильным разработчиком. Моя основная задача — создавать приложения, которые упрощают бизнес-процессы. А еще я обожаю разбирать задачи с собеседований: они как головоломки, которые не только проверяют знание алгоритмов, но и учат видеть не очевидные подходы к коду. Хочу делиться такими кейсами — вдруг это поможет кому-то пройти сложное интервью или вдохновит на новые идеи. Поехали?
Сценарий: вы на собеседовании, ожидаете вопросов про React, WebGL или хотя бы про Event Loop. А вместо этого получаете: «Напишите функцию сложения двух чисел в столбик, но числа передаются как массивы.
«Что? — думаете вы. — Это же уровень начальной школы! Где мои хитрые асинхронные задачи?» Но не спешите радоваться. Я тоже недооценивал эту задачу, пока не попытался реализовать её на JavaScript.
Зачем это JS-разработчику?
Казалось бы: берем, складываем по цифре… Но тут же всплывают вопросы:
-
А если числа разной длины?
-
Что, если одно из чисел — ноль?
-
Как обработать ведущие нули в результате?
-
Куда девать перенос, если после последней цифры осталась единица?
И вот вы уже не код пишете, а разгадываете ребус, где каждая строчка — это шаг через пропасть. А интервьюер смотрит, как вы справляетесь с базовой задачей, которая проверяет:
-
Умение работать с строковыми данными (ведь числа могут быть гигантскими, и bigint не спасет).
-
Понимание алгоритмической логики без подсказок гугла.
-
Внимание к краевым случаям — это как раз то, что ломает продакшен в пятницу вечером.
Подводные камни, или «Ой, а я это не учел»
Давайте разберемся, где можно наступить на грабли:
-
Перенос разряда после последней цифры.Пример: 999 + 1 = 1000. Если забыть про «лишнюю» единицу, случится катастрофа — 000 вместо 1000.
-
Числа разной длины.Что делать, если одно число длиннее? Дополнять нулями слева? Справа? Или обрабатывать «по остатку»?
-
Символы, которые не цифры.А если в строку закралась буква? Нужна ли валидация? (Спойлер: на собеседовании иногда ждут, что вы спросите об этом!)
Ход решения:
Дано:
[5, 4, 4] // 544
[4, 5, 6] // 456
Шаг 1
Единицы: 4 + 6 = 10 → записываем 0, «сдача» 1
Шаг 2
Десятки: 4 + 5 + 1 = 10 → снова 0 и 1
Шаг 3
Сотни: 5 + 4 + 1 = 10 → 0 и 1. Но массивы кончились! Добавляем 1 в начало.
Запишем наши шаги в виде кода
function addArrays(arr1, arr2) {
let i = arr1.length - 1;
let j = arr2.length - 1;
let carry = 0;
const result = [];
Магический цикл: пока есть цифры
while (i >= 0 || j >= 0 || carry > 0) {
const digit1 = i >= 0 ? arr1[i--] : 0; // Берем цифру или 0 (как замену)
const digit2 = j >= 0 ? arr2[j--] : 0;
const total = digit1 + digit2 + carry; // Сумма + 1 из прошлого
carry = Math.floor(total / 10); // Новый 1
result.push(total % 10); // Записываем остаток
}
return result.reverse(); // Переворачиваем
}
Фишка кода: Обратите внимание на i— и j— внутри тернарных операторов — это как два шага назад перед прыжком вперед. И да, carry > 0 в условии цикла — наш стражник, который не даст забыть последний перенос.
5 ошибок, которые превратят ваш код в тыкву
1. переносы
Забыли carry? Получите [0,0,0] вместо [1,0,0,0] для 999+1.
2. массивы
«Мой массив длиннее!» — обрабатывайте края через digit = array[index] || 0.
3. реверс
Не перевернули результат? Поздравляю, ваше 1000 станет 0001!
4. фобия нулей
Пустые массивы? Возвращайте [0] — даже великаны когда-то были нулями.
5. Гонка за оптимизацией
Не пытайтесь умничать — простой цикл здесь элегантнее рекурсий или хитрых методов.
И напоследок: А что, если числа разной длины? Мой алгоритм берет их в оборот через «заполнители»-нули. Как шеф-повар, который из двух разных ингредиентов делает идеальное блюдо — главное правильно добавить «приправу» из переносов.
Попробуйте сами: сверьте свой код с примером [9,9] + [9] = [1,0,8]. Если получилось — вы мастер разрядов! Если нет — ищите потерянный «фантик» где-то в цикле.
Сложность алгоритма
Сколько раз мы пройдемся по циклу? Ровно столько, сколько цифр в самом длинном числе + 1 шаг для возможного переноса.
Пример:
[9,9] (99) + [9] (9) = [1,0,8] (108)
Здесь самое длинное число имеет 2 цифры → 2 шага + 1 шаг для переноса = 3 итерации.
Формула:
O(max(m, n)), где m и n — длины массивов.
Время работы растет линейно — если числа станут в 10 раз длиннее, алгоритм замедлится всего в 10 раз.
Если появился перенос в новый разряд:
max(m, n) + 1 .
Пример:
[5,4,4] (3 цифры) + [4,5,6] (3 цифры) → [1,0,0,0] (4 цифры).
Формула:
O(max(m, n)) для хранения результата + O(1) для переменных (carry, индексы) = итого O(max(m, n)).
Это оптимально! Меньше нельзя — нужно хранить каждую цифру результата. Никакого скрытого мусора в памяти — только цифры и пара временных переменных.
Алгоритм масштабируемый — даже для чисел с миллионом цифр он отработает за доли секунд (если, конечно, JS не сломается ).
Почему это круто?
-
Без скрытых сюрпризов: Нет рекурсий, нет вложенных циклов — только прямой проход.
-
Экономия: Каждая переменная на счету — никаких лишних массивов или хеш-таблиц.
-
Универсальность: Работает для чисел любой длины.
Буду рад, если и вы оцените это решение или поделитесь в комментариях своими вариантами!
Автор: EvgeniiZhukov