Собеседование по программированию  —  сложное испытание, требующее тщательной подготовки. Крупные технологические компании ожидают, что соискатель найдет решение двух задач в течение 45 минут.

Многие кандидаты справляются с заданием и даже предлагают пути реализации своих решений, но получают отказ. Как правило, рекрутеры рекомендуют им улучшить навыки решения задач и оставить отклик в следующий раз. Это ставит большинство кандидатов в тупик, поскольку они не понимают, что пошло не так на собеседовании.

Мне приходилось бывать по обе стороны стола. У каждой компании своя планка приема на работу и свои требования к кандидатам. Они выявляются в процессе дебрифингов, когда интервьюеры обсуждают результаты собеседований.

Обсудим распространенные ошибки на собеседованиях по программированию и подумаем о том, как их избежать.

1. Решение задачи методом “грубой силы”

Я видел много кандидатов, предлагающих на собеседованиях решения “в лоб”. Использование метода “грубой силы” или наивной стратегии  —  это одна из главных причин отказа на собеседованиях. Решения методом “грубой силы” медленны и не требуют применения когнитивных способностей.

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

В реальном мире нужно решать задачи эффективно. Рассмотрим для примера метод Collections.sort(). Он предполагает использование алгоритма Quicksort. Применение Selection Sort или Bubble Sort привело бы к трате большого количества циклов процессора. Кроме того, это замедлило бы работу программы.

Оптимальные решения являются быстрыми и эффективными. Собеседования по программированию проводятся для оценки навыков кандидата в решении задач. Использование правильных структур данных и способность принимать оптимальные решения являются признаками хорошей подготовки. Вот почему необходимо оптимизировать решение  —  никому не понравится, если программа будет работать медленно.

Как оптимизировать решение методом ”грубой силы”?

Перед собеседованием необходимо тщательно изучить структуры данных и алгоритмы. В процессе собеседования кандидат должен использовать все возможности, испробовать все средства. Достаточно беглого взгляда на раздел обсуждения задач Leetcode, чтобы заметить: одна задача может иметь как минимум три решения.

В случае, если вы застряли на какой-то проблеме, используйте следующие три подхода.

  • Оптимальная структура данных/алгоритм

Однажды я задал кандидату задачу по динамическому программированию. Он проявил смекалку и предложил подход, основанный на рекурсии. Я попросил его реализовать это решение, и он смог справиться с задачей раньше времени.

В задаче было несколько пересекающихся подзадач. Он не определил их. Предложенное решение не было оптимальным, и я попросил оптимизировать его. Было похоже, что он не сильно разбирается в решении DP-задач. Он не смог увидеть закономерность и найти оптимальное решение.

Многие задачи имеют определенную закономерность. Фокус в том, чтобы стать экспертом в определении закономерности. Например, задачи на поиск top k-элементов можно решить, используя кучи. Как только паттерн будет определен, понадобится правильная структура данных и алгоритм для его решения.

  • Думайте вслух

Во время решения задачи важно думать вслух. Вы должны проговаривать все, что приходит вам в голову.

Интервьюеры внимательны во время собеседования. Когда кандидат думает вслух, они следят за его мыслительным процессом и могут судить о том, в нужном ли направлении он движется.

Интервьюеры помогают кандидатам и дают им понять, на правильном ли они пути. Таким образом, вы не теряете время и не путаетесь в направлениях.

  • Прислушивайтесь к подсказкам

Одно из распространенных заблуждений заключается в том, что интервьюеры существуют только для того, чтобы отказывать кандидатам. Вы должны избавиться от этого заблуждения во время собеседования. Интервьюеры могут оказаться вашими будущими коллегами в случае, если вы пройдете собеседование.

Они помогают вам. Лично я получал множество подсказок при решении задачи. Правда, эти намеки тонкие и не всегда очевидные.

Однажды мне предложили решить сложную задачу, основанную на строках. У меня в голове было два решения: с trie и с hashmap. Интервьюер спросил меня, является ли решение на основе trie оптимальным. Оказалось, что решение на основе trie не было таковым, поэтому в итоге я использовал hashmap.

Итак, нужно быть внимательным и прислушиваться к подсказкам интервьюера. Они помогают думать в правильном направлении и быстро находить решение.

2. Пренебрежение пограничными случаями

Порой мы слишком уверены в своих решениях и не допускаем того, что код когда-нибудь даст сбой. Часто интервьюеры просят кандидата выявить ошибки в написанном коде.

Посмотрим на приведенный ниже фрагмент кода и попробуем проверить, сработает ли он. Этот код принимает в качестве аргумента LinkedList и возвращает обратный список.

ListNode* reverseList(ListNode* head) {
if(head->next == nullptr)
return head;
ListNode* reversedList = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return reversedList;
}

Этот код пройдет большинство тестовых примеров. Однако он не справится со случаем, когда head является NULL. Это приведет к ошибке сегментации и, следовательно, к неудаче.

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

int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;

while(left <= right) {
int mid = (left + right)/2;
if(nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
return mid;
} else {
right = mid;
}
}
return -1;
}

Попробуйте запустить приведенный выше алгоритм бинарного поиска на следующих входных данных nums= [-1, 0, 3, 5, 9, 12] и target=2. Каков будет результат выполнения?

Приведенный выше код приведет к бесконечному циклу. Код требует исправления в строке 12, right = mid — 1. При написании приведенного выше кода мы забыли присвоить правильное значение right.

Мы неосознанно допускаем ошибки и забываем об этом на собеседовании. Когда мы осознаем свой просчет, уже слишком поздно и собеседование закончено.

Приведенный выше код сработал бы для nums= [1, 10, 12] и target=1. Однако, если код работает в нескольких тестовых случаях, это не значит, что он будет работать всегда.

Как избежать неудачных пограничных случаев?

Применяйте следующие техники для решения граничных задач.

  • Циклы For/While. Убедитесь, что алгоритм завершается, и обработайте ошибки завершения.
  • Проверка на Null. Добавьте проверку на Null при работе с указателями/ссылками. Это важно, если вы имеете дело с задачами, связанными с бинарными деревьями, связанными списками, графами и т.д.
  • Исчерпывающие тестовые случаи. Постарайтесь придумать нетривиальные тестовые случаи. Как обсуждалось в предыдущем разделе, найдите примеры, в которых алгоритм “сломается”.
  • Пробный прогон. Используйте определенные входные данные и пройдитесь по коду так же, как компьютер выполнял бы инструкции. Это позволит избежать большинства ошибок и даст достаточную уверенность в написанном коде.

3. Низкий уровень коммуникационных навыков

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

Однако он молчал в течение всего процесса решения и сразу перешел к написанию кода. Вначале я прервал его и попросил объяснить решение. Он не смог четко аргументировать свой подход и не стал углубляться в детали. Я подумал, что он, возможно, интроверт, и допустил его к следующему этапу. Тем не менее я сделал пометку о его коммуникативных навыках.

Во время дальнейшего собеседования с этим кандидатом выяснилось, что он избегал общения в каждом раунде. Его объяснения были не на высоте, и все сошлись во мнении, что его коммуникативные навыки находятся на низком уровне.

Общение  —  один из самых недооцененных навыков в разработке программного обеспечения. В реальной жизни нам приходится общаться со множеством заинтересованных сторон и продвигать проекты. Нужно представлять идеи для решения сложных задач. Таким образом, навыки сотрудничества и общения не менее важны, чем профессиональные навыки программиста.

Как эффективно общаться?

Во время собеседования кандидаты должны управлять ситуацией и активно вести дискуссию. Для этого можно использовать следующие подходы.

  • Поймите постановку задачи. Уточните, правильно ли вы поняли постановку задачи. Вы должны понять ограничения задачи, т.е. диапазон входных параметров (положительный/отрицательный), максимальное доступное пространство (память), размер входных данных и т.д. Так вы покажете, что внимательны к деталям и не делаете предположений вслепую.
  • Объясните решение. Как только определите оптимальное решение, постарайтесь объяснить его. Вы должны убедиться, что объяснение простое и понятное. Постарайтесь упростить его с помощью примеров. Расскажите также о временной и пространственной сложности вашего решения и о компромиссах, на которые вы идете.

Необходимо помнить, что общение играет важную роль в процессе собеседования. Вы можете быть отличным решателем задач, но, если вы не умеете общаться, это будет считаться тревожным сигналом.

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

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Animesh Gaitonde: Top three mistakes to avoid getting rejected in coding interviews

Предыдущая статьяИспользуем ShakaPlayer в LightningJS
Следующая статьяОбзор полезных инструментов для интроспекции объектов Python