- PVSM.RU - https://www.pvsm.ru -

Я решал LeetCode 600 дней подряд и что из этого вышло

Эволюция программиста

Эволюция программиста

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

А для тех, кто все‑таки не знает, LeetCode [1]— платформа для решения алгоритмических задач разной сложности и тематики, соревнований по скорости и производительности и просто общению с коммьюнити единомышленников по этой теме.

Эта статья - впечатления о моём 600-дневном мараф��не на этой платформе, динамике моих скилов и ответе на главный вопрос “надо ли решать там задачи?”.

Как правило, по отношению к LeetCode люди делятся на два лагеря:

  • ценители и любители LeetCode и алгоритмов;

  • ненавистники LeetCode, которые считают его бесполезной тратой времени.

При этом длительное время я не принадлежал ни первому, ни второму лагерю, хотя иногда и проявлял интерес в сторону LeetCode, но руки так и не доходили. Страшно было даже притрагиваться к такой фундаментальной вещи, как алгоритмы. Но вот, в июльский вечер 2023 года я все-таки решился посмотреть пару задачек - на этих задачках и закончилось мое первое знакомство с платформой. Интерес был утолен, и на какое-то время я успокоился.

На этом мое знакомство с Leetcode закончилось и на некоторое время отстранился.

Все было спокойно, пока мы с другом не заключили спор  - сможем ли мы решить 100 задач до конца 2023 года? А это было 50 задач всего за 1 месяц - декабрь.

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

Челлендж в 100 задач оказался достаточно легким - Новый год мы встречали уже с круглым числом выполненных задач в профиле. Так быстро мы решили не останавливаться - Покоренная вершина стимулировала покорить новую - 200 задач к началу лета (за 5 месяцев).

В конце челленджа в 200 задач мой друг принял решение сойти с дистанции - переизбыток алгоритмов в крови, голове и остальных частях тела вызывал у него дискомфорт и галлюцинации, поэтому в его профиле красуется круглое «200», а я же к этому времени только “разогрелся” и вошел во вкус.

24 февраля 2024 в течении недели LeetCode предлагал неплохие и не очень сложные задачи на дейли челлендже, и у меня случайно получился стрик в районе 10 дней подряд.

Сбивать стрик было как-то жалко - это же целых 10 дней. Так и началась долгая история в 600 дней...


Впечатление

В итоге стрик продлился на 600+ дней и 700+ задач и продолжается на текущий момент: явный интерес был первые 100-200 дней, а далее началась рутина и тяжелая дисциплинированная работа - важно было установить личный рекорд.

Текущая статистика

Текущая статистика

Мой алгоритм в решении был весьма тривиален:

  • Если дейли решаемый - решаю дейли.

  • Если чувствую, что на дейли времени нужно больше, чем обычно - решаю любую попавшуюся под руку задачу.

  • Как правило, под руку попадались Easy/Medium задачи -  этого хватало, чтобы размять свою голову с утра / перед сном.

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

Теперь, видя огромную лесенку из 10+ вызовов std функций языка, больше нет страха, а есть четкая цепочка шаблонных действий.

Тяжелая дорога наверх

Тяжелая дорога наверх

А надо ли оно

Главный вопрос:

А надо л�� оно? Для точного ответа на этот вопрос важно ответить на уточняющие:

  • Вы работаете с высоконагруженной системой?

  • Хотите ли вы попасть в бигтех?

  • Вас привлекают алгоритмы и приносят вам радость?

Если хотя бы на один вопрос вы ответили «да», то LeetCode - подходящий способ прокачать ваши алгоритмические скилы. Хотя бы на 100 задачек. 

Какую пользу можно извлечь из такой авантюры?

  1. Алгоритмическая сложность - ее можно будет увидеть даже невооруженным взглядом и сразу вычислять недостатки алгоритмов / методов и т.п. В продакшн коде это иногда может дать небольшой буст при некоторых сценариях, и ваши пользователи наверняка скажут вам спасибо.

  2. Изучение языка - возможно, лучше способа для этого и не придумать. Человек - ленивое существо, и вам наверняка не захочется в очередной раз писать шаблонный код для тривиальной задачи, а воспользоваться готовой функцией. Для этого придется подробно ознакомится с std функциями вашего языка. Спустя какое-то время вы сможете писать их даже в блокноте без подсказок ide, что иногда требуют на некоторых собеседованиях.

    Junior код:

    fun countChars(s: String): Map<Char, Int> {
        val res = mutableMapOf<Char, Int>()
        for (c in s) {
            if (res.contains(c)) res[c] = res[c]!! + 1
            else res[c] = 1
        }
        return res
    }

    Senior код:

    fun countChars(s: String): Map<Char, Int> =
        s.groupingBy { it }.eachCount()
  3. Паттерны - любой базовый алгоритм превратится для вас в набор шаблонных действий разной очередности. С�� временем  понимать чужие решения станет в разы проще.

  4. Структуры данных - если это ваша слабая сторона, то LeetCode поможет исправить это: массивы, списки, хэш таблицы, деревья, графы и даже стэк с очередью будут ждать вас в решениях самых разных задач. 

По началу придется смотреть чужие решение — по ним можно понять оптимальные пути решения проблем и увидеть шаблоны.

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

LeetCode — это просто один из удобных и порой интересных инструментов, где можно это освоить и «посоревноваться» с другими.


Почему не hard

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

Плохо ли это? Опять же все зависит от ваших целей и возможностей.

Если ваша цель - подготовиться к собеседованию в Google или другие подобные компании, то это будет маст хев. Но для базового понимания алгоритмов и игры "в долгую" это явно будет лишним - заканчивая 8-часовой рабочий день, вы вряд ли захотите потратить еще пару часов на решение головоломки уровня hard для достижения эфемерной цели "узнать алгоритмы" Здесь больше подходит правило привычки, а не быстросгорающей мотивации, на которой далеко не уедешь.

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


Решения

Из всех решений, что я видел за это время на LeetCode, я выделил следующие группы:

  1. Максимально производительные и правильные решения.

    Они основаны на паттернах или даже математике и дают "100% beats" в ваше решение: std функции языка не используются, а «изобретается велосипед», но весьма производительный. 

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

  2. Элегантные и непроизводительные решения.

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

    Такой код не претендует на эффективность, но демонстрирует красоту языка и уровень его познания у написавшего.

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

  3. «Чтобы просто работало».

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

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

Для примера - сравним подходы к решению одной и той же задачи из подборки Яндекса [2]

Производительный вариант (~1ms):

fun canPlaceFlowers(flowerbed: IntArray, n: Int): Boolean {
    if (n == 0) return true

    var remaining = n
    var i = 0

    while (i < flowerbed.size) {
        if (flowerbed[i] == 0) {
            val prev = if (i == 0) 0 else flowerbed[i - 1]
            val next = if (i == flowerbed.size - 1) 0 else flowerbed[i + 1]

            if (prev == 0 && next == 0) {
                flowerbed[i] = 0
                remaining--
                if (remaining == 0) return true
                i++
            }
        }
        i++
    }
    return remaining == 0
}

Лаконичный вариант (~30ms):

fun canPlaceFlowers(flowerbed: IntArray, n: Int): Boolean = flowerbed
    .withIndex()
    .filter { it.value == 1 }
    .map { it.index }
    .let { listOf(-2) + it + (flowerbed.count()+1) }
    .zipWithNext { i, j -> (j - i - 2) / 2 }
    .sum() >= n

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

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

  1. Первое решение заметно выигрывает в скорости.

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

  3. Их алгоритмическая сложность одинакова и равна O(N) из-за полного обхода.

  4. Левое решение заметно выигрывает по потребляемой памяти (O(1) vs O(n)), из-за отсутствия преобразований структур и работы с исходным массивом.

Дилемма LeetCode'ра

Дилемма LeetCode'ра

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

  1. prefix sum

  2. sliding window

  3. hash table

  4. dfs

  5. binary search

  6. two pointer

  7. stack & queue

Останавливаться на них мы не будем, но если вы разберетесь в них, то большая часть задач LeetCode будет вам под силу.

С базовыми интерпретациями можно ознакомиться в прекрасной и легкой книжке - «Грокаем алгоритмы».


Систематичность

В чем же секрет? Да, на самом деле, ни в чем.

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

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

Единственный минус при таком раскладе -  достаточно часто это просто задача ради задачи: она не учит чему-то новому и уже не развивает какие-то скилы в алгоритмах или языке.

Как я и писал выше, где-то на 200+ день это просто превратилось в рутинную работу, которую я в том или ином виде делаю каждый день.

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

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

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


Собеседования

Цель решения этих задачек может быть разной:

  • изучение функций языка;

  • изучение алгоритмов;

  • небольшая разминка для мозга [3];

  • просто банальная состязательность в решениях между собой;

  • тренировка к прохождению собеседований в бигтех компании по типу Google, Yandex и подобных.

Последняя как раз и является самой популярной причиной.

Поможет ли вам в этом LeetCode? Скорее да, чем нет. Все зависит от подхода к решению задач.

Основная цель - понять какой-то паттерн решения и получить навык применять его в решении аналогичной задачи

Для такой тренировки отлично может подойти раздел курсов [4], где задачи сгруппированы по принципу их алгоритмов и поможет вам заметить между ними общие черты.

Иногда даже сами HR высылают список задач на тренировку к алгоритмической секции собеседования (Yandex).

Исходя из опыта коллег, для порога прохождения собеседований необходимо решить хотя бы 100 задач easy/medium сложности. При этом это не дает никаких гарантий и некоторым может понадобится в разы больше.

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

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

Приступ синдрома самозванца

Приступ синдрома самозванца

П.с. картинка взята из чужой статьи про литкод [5]

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


Советы

И наконец от себя хотел бы дать ряд советов:

  1. Определитесь с целью.

    Правильная цель задаст нужный ритм вашей работе!
    Возможно, оно вам просто не нужно.

  2. Начните с простого.

    Мало кто сможет сходу решить большинство medium и hard задач.
    Обращайте внимание на Acceptance Rate в задачах: чем выше процент, тем больше вероятность решить ее.

  3. Не стесняйтесь смотреть чужие решения.

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

  4. Экспериментируйте.

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

  5. Привычки сильнее мотивации.

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

  6. Делитесь своими решениями.

    Если вы пишете на популярном языке, то можете получить одобрение или ряд советов от единомышленников. Это поможет влиться в это сообщество и прокачать свои скиллы.

Я решал LeetCode 600 дней подряд и что из этого вышло - 9

Итоги

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

LeetCode - это отдельный мир, в котором свои правила игры и не так много соприкосновений с реальностью.

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

Автор: qveex

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/algoritmy/435035

Ссылки в тексте:

[1] LeetCode : https://leetcode.com/problemset/

[2] одной и той же задачи из подборки Яндекса: https://leetcode.com/problems/can-place-flowers/description/

[3] мозга: http://www.braintools.ru

[4] раздел курсов: https://leetcode.com/explore/learn/

[5] чужой статьи про литкод: https://habr.com/ru/companies/geekfactor/articles/597035/

[6] Источник: https://habr.com/ru/companies/betboom/articles/959246/?utm_source=habrahabr&utm_medium=rss&utm_campaign=959246