Сегодня на рынке 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)


  1. dan4ik316
    13.06.2024 10:51

    Интересный момент про "Нет вложенным циклам". Легко нарваться случайно, когда глаз замыливается.


  1. 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;
    }


  1. VasilievVictor
    13.06.2024 10:51

    можно и так, за несколько минут набросал:

    можно и короче, но тут вроде наглядно видны этапы работы алгоритма. Писал в без проверки, как на собеседовании, мог ошибиться )), так как думать и гонять в голове тесты не охота. А в примере, в статье, лишняя память задействована, да и на первый взгляд плохо читается, я правильно понимаю, что удаляется постоянно первый элемент? А что потом происходит с массивом, перестраивается или за O(1) эта операция проходит?


  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 - большой разницы нет. Никто этим не заморачивается.

    Обычно могут спросить другое: "А какова сложность вашего алгоритма по требуемой памяти?" Вот это может быть существенным. Бывает так, что быстрый алгоритм требует много памяти (например, под словарь со счётчиками или указателями) и тут уже нужно искать компромисс между скоростью алгоритма и потребляемой им памятью.


  1. slebkov
    13.06.2024 10:51
    +1

    Статья хорошая, но как будто больше про алгоритмический собес. Мне вот недавно с live-coding пришел отзыв "компилировал код в терминале, не пользуясь хоткеями vscode" – к такому меня жизнь не готовила.


    1. sergiodev
      13.06.2024 10:51

      Гы, а что за контора?

      Я сам редко учу горячие клавиши - только если по 100 раз в день приходится пользоваться, например. А иначе зачем голову забивать этим мусором? Тем более в VS Code у каждого расширения они разные.


  1. jibunnoeiko
    13.06.2024 10:51
    +1

    "Собеседующий не этого ожидал", а откуда кандидат должен знать что он ожидает, решил поставленную задачу и все на этом, к чему этот лайф рефакторинг?)


  1. Fadeadx
    13.06.2024 10:51

    Это live-coding, пиджаки с какими-то details. И это всё on design


  1. EBetin
    13.06.2024 10:51

    У меня безоговорочный приоритет получил бы кандидат, который, перед тем как писать код задал бы несколько вопросов.

    • какого размера массивы в параметрах, есть ли смысл тут писать оптимальное решение в ущерб читаемости кода

    • нет ли возможности отрефакторить, вызывающий код так, чтобы избежать написания алгоритма и обойтись сортировкой из стандартной библиотеки

    • приватный это метод или публичный, нужна ли проверка на то, что массивы отсортированы

    Ну и так далее. Это если конечно мы проверяем "насколько хорошо варит голова кандидата в бою".


    1. VasilievVictor
      13.06.2024 10:51

      То есть, если по условию задачи массивы отсортированы, а собеседуемый спрашивает, нужно ли это программно проверять, это плюс? Насчет размера согласен, но тут же поступит ответ, что массивы очень большого размера и требуется оптимальный алгоритм, быстрее чем n log n.

      Выше привел пример решения на свифт, в котором все читаемо и просто. Его тоже можно изменить по пожеланиям собеседующего, уменьшив или немного оптимизировав. Но на мой взгляд, нормально. Прошел бы с таким решением собеседование или нет?


      1. EBetin
        13.06.2024 10:51

        Ваш код на порядок или два лучше чем то, то в статье. По крайней мере он читается на лету, в отличии от. Код, который приведен как пример грамотного решения в статье, не должен пройти ревью. Да, я считаю, что разработчик, начиная с уровня мидла должен в этом случае попросить контекст, в котором вызывается метод, или явно указать, что он бы в первую очередь попытался бы отрефакторить код снаружи. Насчет входных параметров, если эта функция вызывается из любого места кода, то это просто вопрос времени, когда ее кто-то вызовет с неотсортированными массивами, и тогда первый неоптимальный вариант не является неоптимальным, он будет единственным правильным ответом. Если бы собеседуемый указал на это, это было бы ему в плюс. Насчет прошли бы или нет, это зависело бы от других факторов а не только от того как вы написали эту функцию. Возможно в голове у меня отложилось бы, что вы скорее склонны писать понятный и читаемый код, а автор статьи код пишет тяжелый и неприятный.


        1. EBetin
          13.06.2024 10:51

          del


  1. Hivemaster
    13.06.2024 10:51

    Сегодня на рынке IT трудно представить собеседование без того или иного варианта Live Coding

    Громкое заявление. Сегодня на рынке IT трудно представить собеседования с такими непопулярными подходами, ведь у кандидата за воротами очередь желающих предложить оффер.


  1. souls_arch
    13.06.2024 10:51

    В первой задаче неплохо бы выдать в качестве ответа оба варианта. Первый: для реальной разработки, как поддерживаемый и читаемый код на основе уже имеющихся средств ЯП. Второй: как нечитабельное, сложноподдерживаемое и обычно никому не нужное изобретение велосипеда. Еще и вводящее в заблуждение читающего код своим названием. Так как название функции неплохо бы сменить на mergeSortedArrays иначе нихрена не будет работать, если мы в аргументы зарядим несортированные массивы. Во входных параметрах подсказки для этого тоже нет. Навести прочий рефакторинг.

    Ну и изначально, как писали уже выше, нужно интересоваться целями кода:

    1) требования/ ограничения к диапазону, типу и т.д данных (constraints)

    2) временные рамки на задачу

    3) цель (где, как и с какими constraints будет работать данный код в целом, т.е функция у нас не в вакууме)