Сегодня на рынке IT трудно представить собеседование без того или иного варианта Live Coding — будь то алгоритмическая задача или рефакторинг уже написанного кода. Цель этого мероприятия — понять, насколько хорошо варит голова кандидата в бою. Ведь теорию, по сути, любой может вызубрить и ответить на все вопросы на ура. А вот как он мыслит и пишет код — простыми вопросами никак не определить.
Часто Live Coding вселяет ужас в кандидата, бывает, что от волнения он просто забывает даже самые простые пути решения той или иной задачи. Мы с вами не будем разбирать конкретные задачи и то, как их можно решить, потому что собеседующие становятся все более изощренными, и предугадать, что для вас подготовили, практически невозможно. Но мы можем исключить самые частые ошибки и получить подсказки к решению задачи, прочитав только требования.
Поэтому сегодня мы с вами разберем самые частые ошибки кандидата, а также в конце видео я дам несколько советов, которые помогут вам в будущем решать любые задачи на раз-два.
Об основных ошибках собеседующихся и советах рассказал lead iOS-разработки — Артур Михайлов.
Для тех, у кого нет времени на чтение материала, рекомендуем посмотреть видеоролик:
Основные ошибки, которые совершают кандидаты на live-coding сессиях
Игнорирование входных данных
Самой распространенной ошибкой можно назвать игнорирование входных данных. Это случай, когда задача или проблема в качестве входного параметра имеет уже готовую (например, отсортированную) структуру данных, которую при решении проблемы или задачи кандидат упускает из виду и меняет структуру под удобную себе. Это ведет к тому, что потенциально линейное решение задачи или проблемы приводится к более сложному.
Пример
Необходимо написать функцию, которая сложит два отсортированных массива в один единый, также отсортированный массив
Самое просто и лаконичное решение может выглядеть следующим образом:
С точки зрения читаемости кода это можно считать эталонным решением, но мы упускаем из виду главное преимущество входных параметров алгоритма — массивы уже отсортированы. Одно только это условие должно натолкнуть в первую очередь на мысль о том, что дальнейшей сортировки в процессе решения задачи стоит избегать, так как сортировка — это процесс с нелинейной сложностью (в лучшем случае O(n log n)).
Решение задачи за линейное время, конечно, потребует больше кода и внимательности, но в этом и заключается, на самом деле, задача — реализовать оптимальный с точки зрения производительности алгоритм. Решение может иметь следующий вид, главным преимуществом которого будет линейная сложность выполнения (O(n)).
Линейная сложность, двойное время выполнения
Зачастую при оценке сложности алгоритма мы можем прийти к результату типа O(n), при том, что время на выполнение алгоритма будет x * n, где x — количество последовательных итераций. Это магическое число x должно быть неявным индикатором и подсказкой для кандидата о том, насколько оптимально реализован его алгоритм. Можно взять себе за правило, что число x не должно превышать 2 или 3. Если в процессе реализации появляется понимание того, что число x уходит далеко за пределы диапазона 2–3, что-то идет не так, и стоит пересмотреть свое решение.
Нет вложенным циклам
Звучит тривиально и просто, но можно легко попасть в ловушку вложенного цикла, сам того не осознавая. Рассмотрим простой пример.
На первый взгляд может показаться, что нас всего один цикл и сложность выполнения будет линейной. Но стоит обратить внимание на функцию contains(:), как тут же становится понятно, что мы получили цикл в цикле, а это, в свою очередь, дает сложность O(n2).
Полезные советы
Задавай вопросы
Прежде чем приступать к решению задачи или проблемы, стоит ее проанализировать с точки зрения ограничений по использованию дополнительных структур данных и крайних случаев (edge cases). Если в задаче они явно не описаны, то стоит начать анализ с вопросов по этим пунктам. Это поможет понять, какое решение ожидается от кандидата и в целом покажет, что кандидат умеет мыслить систематически (вместо того, чтобы сразу бросаться писать код).
Думай медленно, решай быстро
Это принцип, описанный в одноименной книге Даниэля Канемана, который хорошо применим к решению задач на live coding. Как только задали все уточняющие вопросы, стоит собрать ответы на них воедино и проанализировать в голове свое решение, проговаривая каждый шаг ВСЛУХ. Это поможет наладить контакт с собеседующим и сразу позволит понять, в правильном ли направлении идет ход мыслей
Пиши просто, рефакторинг позже
Самое главное в задаче или проблеме — это то, что решение должно быть РАБОЧИМ. Решить задачу или проблему не всегда получится решить лаконично и красиво с точки зрения кода. Все примеры «идеального» решения на LeetCode и Codewars не были написаны сходу без правок — они итеративно рефакторились до того момента, когда решение приобретет лаконичный вид. Учитывая этот факт, хорошим подходом будет в первую очередь написать РАБОЧЕЕ решение задачи, а уже потом, по договоренности с собеседующим, отрефакторить код.
Единственное, о чем стоит помнить, — это то, что «грязное» решение будет по сложности выполнения приближенным к оптимальному. Если «грязное» решение имело сложность O(n2), а рефакторинг привел к уменьшению сложности, то это уже не рефакторинг, а новый подход к решению задачи.
Избегай скрытой сложности
Вне зависимости от языка программирования в решении задачи может выглядеть разумным использование функций высшего порядка с нелинейной сложностью выполнения. Примерами таких функций могут быть sort, сложность которого является O(n log n).
Оценивай потенциальную сложность с данным временем
Собеседующий описывает задачу и обязательно говорит время, за которое задача должна быть решена. Это само по себе должно послужить сигналом о том, с какой сложностью выполнения мы столкнулись. Если это время 10–15 минут, то, скорее всего, ожидаемая сложность — O(n). Если времени дается больше, например 30 минут, то, скорее всего, решение не будет таким уж простым, и сложность выполнения может выходить за рамки O(n). Но чаще всего все же алгоритмические задачи будут сводиться к линейной сложности, если вы, конечно, не собеседетесь в «Желтую компанию» с красной буквой Я или в сеть компаний FAANG (Facebook, Apple, Amazon, Netflix, Google) на сеньорскую позицию.
Заключение
Мы разобрали самые частые ошибки, которые, я надеюсь, вы не допустите в будущем при прохождении собеседований. Не забывайте также и про полезные советы, которые всегда будут для вас шпаргалкой и помогут успешно проходить собеседования, в частности Live Coding. Если у вас из опыта есть что добавить, жду ваших случаев в комментариях, с удовольствием почитаю. С вами был Артур, до новых встреч.
Автор: Артур Михайлов, lead iOS-разработки в «Технократии»
Также подписывайтесь на наш телеграм‑канал «Голос Технократии». Каждое утро мы публикуем новостной дайджест из мира ИТ, а по вечерам делимся интересными и полезными статьями.
Комментарии (14)
NeoCode
13.06.2024 10:51+2Не знаю на каком там языке, похоже на Go, но стрелочки и двоеточия которых в Go точно нет. (UPD судя по контексту "iOS разработка" это Swift :) ) Бросилось в глаза что какой-то слишком большой объем кода, какие-то префиксы, ни черта не понятно. ИМХО должно быть короче. На С++:
std::vector<int> sorted_merge(const std::vector<int> &A, const std::vector<int>&B) { int ai=0, bi=0, ci=0; std::vector<int> C(A.size() + B.size()); while(ai<A.size() && bi<B.size()) C[ci++] = A[ai] < B[bi] ? A[ai++] : B[bi++]; while(ai<A.size()) C[ci++] = A[ai++]; while(bi<B.size()) C[ci++] = B[bi++]; return C; }
VasilievVictor
13.06.2024 10:51можно и так, за несколько минут набросал:
можно и короче, но тут вроде наглядно видны этапы работы алгоритма. Писал в без проверки, как на собеседовании, мог ошибиться )), так как думать и гонять в голове тесты не охота. А в примере, в статье, лишняя память задействована, да и на первый взгляд плохо читается, я правильно понимаю, что удаляется постоянно первый элемент? А что потом происходит с массивом, перестраивается или за O(1) эта операция проходит?
CrazyElf
13.06.2024 10:51+1Если в процессе реализации появляется понимание того, что число x уходит далеко за пределы диапазона 2–3, что-то идет не так, и стоит пересмотреть свое решение.
Что-то первый раз такое слышу. Если число
x
фиксированное, то какое бы большое оно ни было, сложность решенияO(x*n)
всё-равно будет считатьсяO(n)
. Да, для малыхn
разница может быть существенна, но для малыхn
обычно и не заморачиваются с алгоритмами. А когдаn
это миллионы и миллиарды, линейное решение всё-равно будет лучше, чемO(n*log n)
и тем болееO(n^2)
. Конечно, еслиx
не будет само порядка миллиона при этом, но что же это за такой алгоритм вообще с константным числом в миллион шагов? А когдаx
это 2, 3, 10 или даже 100 - большой разницы нет. Никто этим не заморачивается.Обычно могут спросить другое: "А какова сложность вашего алгоритма по требуемой памяти?" Вот это может быть существенным. Бывает так, что быстрый алгоритм требует много памяти (например, под словарь со счётчиками или указателями) и тут уже нужно искать компромисс между скоростью алгоритма и потребляемой им памятью.
slebkov
13.06.2024 10:51+1Статья хорошая, но как будто больше про алгоритмический собес. Мне вот недавно с live-coding пришел отзыв "компилировал код в терминале, не пользуясь хоткеями vscode" – к такому меня жизнь не готовила.
sergiodev
13.06.2024 10:51Гы, а что за контора?
Я сам редко учу горячие клавиши - только если по 100 раз в день приходится пользоваться, например. А иначе зачем голову забивать этим мусором? Тем более в VS Code у каждого расширения они разные.
jibunnoeiko
13.06.2024 10:51+1"Собеседующий не этого ожидал", а откуда кандидат должен знать что он ожидает, решил поставленную задачу и все на этом, к чему этот лайф рефакторинг?)
EBetin
13.06.2024 10:51У меня безоговорочный приоритет получил бы кандидат, который, перед тем как писать код задал бы несколько вопросов.
какого размера массивы в параметрах, есть ли смысл тут писать оптимальное решение в ущерб читаемости кода
нет ли возможности отрефакторить, вызывающий код так, чтобы избежать написания алгоритма и обойтись сортировкой из стандартной библиотеки
приватный это метод или публичный, нужна ли проверка на то, что массивы отсортированы
Ну и так далее. Это если конечно мы проверяем "насколько хорошо варит голова кандидата в бою".
VasilievVictor
13.06.2024 10:51То есть, если по условию задачи массивы отсортированы, а собеседуемый спрашивает, нужно ли это программно проверять, это плюс? Насчет размера согласен, но тут же поступит ответ, что массивы очень большого размера и требуется оптимальный алгоритм, быстрее чем n log n.
Выше привел пример решения на свифт, в котором все читаемо и просто. Его тоже можно изменить по пожеланиям собеседующего, уменьшив или немного оптимизировав. Но на мой взгляд, нормально. Прошел бы с таким решением собеседование или нет?
EBetin
13.06.2024 10:51Ваш код на порядок или два лучше чем то, то в статье. По крайней мере он читается на лету, в отличии от. Код, который приведен как пример грамотного решения в статье, не должен пройти ревью. Да, я считаю, что разработчик, начиная с уровня мидла должен в этом случае попросить контекст, в котором вызывается метод, или явно указать, что он бы в первую очередь попытался бы отрефакторить код снаружи. Насчет входных параметров, если эта функция вызывается из любого места кода, то это просто вопрос времени, когда ее кто-то вызовет с неотсортированными массивами, и тогда первый неоптимальный вариант не является неоптимальным, он будет единственным правильным ответом. Если бы собеседуемый указал на это, это было бы ему в плюс. Насчет прошли бы или нет, это зависело бы от других факторов а не только от того как вы написали эту функцию. Возможно в голове у меня отложилось бы, что вы скорее склонны писать понятный и читаемый код, а автор статьи код пишет тяжелый и неприятный.
Hivemaster
13.06.2024 10:51Сегодня на рынке IT трудно представить собеседование без того или иного варианта Live Coding
Громкое заявление. Сегодня на рынке IT трудно представить собеседования с такими непопулярными подходами, ведь у кандидата за воротами очередь желающих предложить оффер.
souls_arch
13.06.2024 10:51В первой задаче неплохо бы выдать в качестве ответа оба варианта. Первый: для реальной разработки, как поддерживаемый и читаемый код на основе уже имеющихся средств ЯП. Второй: как нечитабельное, сложноподдерживаемое и обычно никому не нужное изобретение велосипеда. Еще и вводящее в заблуждение читающего код своим названием. Так как название функции неплохо бы сменить на mergeSortedArrays иначе нихрена не будет работать, если мы в аргументы зарядим несортированные массивы. Во входных параметрах подсказки для этого тоже нет. Навести прочий рефакторинг.
Ну и изначально, как писали уже выше, нужно интересоваться целями кода:
1) требования/ ограничения к диапазону, типу и т.д данных (constraints)
2) временные рамки на задачу
3) цель (где, как и с какими constraints будет работать данный код в целом, т.е функция у нас не в вакууме)
dan4ik316
Интересный момент про "Нет вложенным циклам". Легко нарваться случайно, когда глаз замыливается.