Leetcode 0

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

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

К моей статье на Хабре1 с заметками по подготовке к алгоритмической секции прилетел десяток гневных комментариев, хотя ни один из их авторов к нам на собеседование не приходил. Мы не будем уподобляться их авторам и попробуем накопить знания в этой области и выработать в себе умение их применять не только на собеседовании, но и на практике.

Чтобы поделиться своим опытом подготовки, я решил посвятить этому вопросу серию заметок в блоге. В этой обсудим сам сервис leetcode2.

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

Такой “гринд”, кроме цифры около вашего аватара, ничего по сути не даёт. Попытки решать задачи прямо в редакторе на сайте тоже. Во-первых, редактор, хоть и обладает отладчиком, не подсвечивает опечатки и даёт никаких подсказок с именами функций и методов. Этот подход похож на попытку заставить код работать, описанный Робертов Мартином в книге “Чистый код”. Во-вторых, после отправки такого решения, понимания не остаётся. Только чувство “победы” над задачей.

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

task-slug-1/
  go/
    solution.go
    solution_test.go
    go.mod
  python/
    solustion.py
    solutions_test.py
task-slug-2/
  ...

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

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

Когда проект стал довольно большим, а у меня на момент написания статьи решено более двухсот задач, я решил перестроить структуру:

...
├── go
│   ├── task-slug-1
│   ├── task-slug-2
├── kotlin
│   ├── task-slug-1
│   ├── task-slug-2
├── mysql
│   ├── task-slug-1
│   ├── task-slug-2
├── python
│   ├── task-slug-1
│   ├── task-slug-2
...

Я разделил задачи по языкам и стал работать с каждой задачей как с отдельным проектом. Это позволило не отвлекаться на большую древовидную структуру в навигаторе и разгрузило IDE, так как индексация стала занимать приличное время, в основном, конечно, из-за JDK-языков.

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

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

В следующих заметках поговорим о практических вещах. До скорого!