Собеседование по программированию — сложное испытание, требующее тщательной подготовки. Крупные технологические компании ожидают, что соискатель найдет решение двух задач в течение 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. Низкий уровень коммуникационных навыков
Несколько лет назад, проводя собеседование с кандидатом, я задал ему две задачи. Он смог решить обе, и в каждом случае выбрал оптимальный способ.
Однако он молчал в течение всего процесса решения и сразу перешел к написанию кода. Вначале я прервал его и попросил объяснить решение. Он не смог четко аргументировать свой подход и не стал углубляться в детали. Я подумал, что он, возможно, интроверт, и допустил его к следующему этапу. Тем не менее я сделал пометку о его коммуникативных навыках.
Во время дальнейшего собеседования с этим кандидатом выяснилось, что он избегал общения в каждом раунде. Его объяснения были не на высоте, и все сошлись во мнении, что его коммуникативные навыки находятся на низком уровне.
Общение — один из самых недооцененных навыков в разработке программного обеспечения. В реальной жизни нам приходится общаться со множеством заинтересованных сторон и продвигать проекты. Нужно представлять идеи для решения сложных задач. Таким образом, навыки сотрудничества и общения не менее важны, чем профессиональные навыки программиста.
Как эффективно общаться?
Во время собеседования кандидаты должны управлять ситуацией и активно вести дискуссию. Для этого можно использовать следующие подходы.
- Поймите постановку задачи. Уточните, правильно ли вы поняли постановку задачи. Вы должны понять ограничения задачи, т.е. диапазон входных параметров (положительный/отрицательный), максимальное доступное пространство (память), размер входных данных и т.д. Так вы покажете, что внимательны к деталям и не делаете предположений вслепую.
- Объясните решение. Как только определите оптимальное решение, постарайтесь объяснить его. Вы должны убедиться, что объяснение простое и понятное. Постарайтесь упростить его с помощью примеров. Расскажите также о временной и пространственной сложности вашего решения и о компромиссах, на которые вы идете.
Необходимо помнить, что общение играет важную роль в процессе собеседования. Вы можете быть отличным решателем задач, но, если вы не умеете общаться, это будет считаться тревожным сигналом.
Чтобы улучшить навыки общения, можно потренироваться говорить перед зеркалом и провести пробное собеседование с другом. Такие тренинги помогут преодолеть страх перед собеседованием, если вы давно не сталкивались с подобным испытанием.
Читайте также:
- Какие вопросы задавать HR-менеджеру во время собеседования
- То, чего вам никто не расскажет о поиске работы, связанной с анализом данных
- Что мы ожидаем от разработчиков на каждом уровне
Читайте нас в Telegram, VK и Дзен
Перевод статьи Animesh Gaitonde: Top three mistakes to avoid getting rejected in coding interviews