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

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

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

Эта статья - впечатления о моём 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. «Чтобы просто работало».

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

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

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

Производительный вариант (~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-дневного путешествия, могу сказать, что базовые паттерны в разных их проявлениях становятся видны достаточно быстро, однако достаточно сложные вариации и комбинации нескольких иногда могут вызвать явные затруднения.

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Советы

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

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

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

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

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

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

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

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

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

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

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

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

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


Итоги

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

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

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

Комментарии (13)


  1. Warperus
    29.10.2025 09:21

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

    Алгоритмическая сложность "производительного" алгоритма O(N), потому что сортировка 26 значений в O-нотации это всё ещё O(1).

    Потребление памяти обоими алгоритмами O(N), хотя константа у левого и меньше. Наивно полагать, что на больших N слово влезет в память константного размера.

    Попробуйте прогнать эти решения со строкой в миллионы символов.


    1. qveex Автор
      29.10.2025 09:21

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

      По сложности тоже согласен. Не самый удачный пример выбрал все-таки. Постараюсь поправить этот момент


    1. domix32
      29.10.2025 09:21

      Потребление памяти обоими алгоритмами O(N),

      O() потребление памяти подразумевает сколько дополнителной памяти будет выделено в худшем случае при выполнении алгоритма, то есть исключая память на входные данные. Если это несколько переменных и исходный входные данные меняются на месте или происходит выделение памяти некоторого фиксированного размера, не имеющего зависимости от размера входных данных, то это всё ещё константа. У производительного примера именно так и происходит, а у лаконичного происходит кучка почти N константных небольших аллокации (listOf(-2)) в лямбде, которая почти сразу же освобождаются. И вероятно есть скрытые аллокации в zipWithNext, из-за чего и страдает производительность, но эти аллокации делают как раз C*N дополнительных аллокаций, что приводит к амортизированному O(N) по памяти для второго алгоритму и O(1) для производительного.

      Когда решал задачи на Rust, старался сделать так, чтобы код был лаконичным и производительным. 99.9% задач решались как раз вот такими вот map/filter цепочками с ленивым вычислением. Отслеживание лишних аллокаций - как раз путь к производительному коду. Есть алгоритмы, которые нельзя записать лаконично и производительно, но большинство алгоритмов вполне. По крайней мере в рамках задач на литкоде, где обработка ошибок/исключений обычно не требуется.


  1. stas_dubich
    29.10.2025 09:21

    Выглядит как литкод ради литкода)

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

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

    Человеческий мозг это как уникальный процессор, который способен творить, но почему то из него пытаются сделать hdd, заучить функции языка, заучить алгоритмы которые с вероятностью 99% никогда не пригодятся и тд


    1. qveex Автор
      29.10.2025 09:21

      Выглядит как литкод ради литкода)

      Именно так это и ощущалось!)

      На самом деле, примеров не так уж и много

      Для себя отметил такие вещи:

      • Стал обращать внимание на последовательность вызовов - если можно изменить их очередность и кол-во обрабатываемых данных сократиться.

      • Аналогично с алг. сложностью - чаще использовать set, map и иногда деревья (их редко). Т.е. мозг начал обращать внимание на вещи, которые большинство пропустит мимо.

      • Заметно подрос уровень владением языка - для чего раньше придумывался велосипед, сейчас я знаю как сделать функциями из коробки (группировки, окна и т.п.). Это может и просто с опытом прийти, но многим проще писать по старинке как привыкли, а тут будет небольшой толчок.

      Как и писал в статье, очень это специфический инструмент обучения, но мне почему-то зашло (не во всех аспектах)


    1. ImABug
      29.10.2025 09:21

      Литкод просто инструмент. Площадка, которая способна что-то тебе дать, но не обязательно она тебе это даст) Все же зависит от человека. Для кого-то это литкод ради литкода, а кто-то увидев новое решение пойдет как минимум почитает о нем. Я первое время с интересом шерстил гугл когда встречал новые для себя вещи.

      Я спустя 700 задач могу сказать, что по сравнению с 1,5-2 годами назад натыкаясь иногда на свои старые решения смотрю на них с улыбкой и понимаю сколько же в них лишнего, буквально натягивание совы на глобус по сравнению с тем как решаю сейчас. Стал использовать более подходящие структуры данных, стал почти моментально видеть хотя бы верхнюю границу big О того или иного кода, не говоря о том, что узнал кучу алгоритмов, которые на порядок бустят мои старые решения. Да и банально, это учит тебя постепенно смотреть на задачу под разным углом. На литкоде не мало задач, которые разумно решаются фактически только одним способом, но есть и те, где можно раскрутить и реализовать по-разному и все варианты хороши.

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


  1. itatarchenkoru
    29.10.2025 09:21

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

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


  1. Viacheslav01
    29.10.2025 09:21

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

    А потом такое, для запуска нашего notepad вам необходимо 100500 гб винта, 100500 гб мозгов и камень в 32 ядра о 5 ггц.


    1. qveex Автор
      29.10.2025 09:21

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

      Даже использование базовых функций map, sumOf, reduce и т.п. накладывает какой-то отпечаток на производительность. Несмотря на то, что алгоритм идентичен, "модное" решение практически всегда будет заметно проигрывать в скорости и памяти.

      Аналогично и со стримами java, насколько я знаю

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


  1. AlexeyK77
    29.10.2025 09:21

    fun minimumPushes(word: String) = word
        .groupingBy { it }
        .eachCount()
        .values
        .sortedDescending()
        .chunked(8)
        .foldIndexed(0) { i, acc, counts -> acc + counts.sum() * (i + 1) }

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

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


  1. Zara6502
    29.10.2025 09:21

    тут же на хабре узнал о проекте highload.fun (можете считать за рекламу). а вот leetcode мне сильно не понравился, неудобен, постоянная борьба с интерфейсом.


    1. Progmech
      29.10.2025 09:21

      Интересно, каким образом в небольшой список языков этой платформы попал C#?


      1. Zara6502
        29.10.2025 09:21

        спросите у автора, там недавно zig добавили по просьбе трудящихся. я пишу в основном на C#, мне нра.