Русский
Русский
English
Статистика
Реклама

Яндекс практикум

Ищем максимальную разницу между соседями. User-friendly-разбор задачи по алгоритмам

08.12.2020 12:05:45 | Автор: admin
Привет, Хабр!

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



Сегодня мы посмотрим на одну красивую задачу по алгоритмам. Не будем отпугивать людей от работы с алгоритмами на самом старте, поэтому в нашем разборе не будет ни развесистых деревьев отрезков, ни разномастных оптимизаций задачи о рюкзаке, ни вероятностных тестов на простоту. User-friendly algos.

Вот задача: найти максимальную разницу между соседями.

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

Сложно? Можете попробовать сделать это до того, как нажмёте Читать дальше, а потом проверим решение.

Итак, поехали. Максимальная разница между соседями.

Рассмотрим пример:
Пусть дан массив из N=11 элементов.
A = [-1, -3, 10, 20, 21, 4, 3, 22, 10, -2, 15]

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

Если используем qsort или mergesort, то временная сложность составит O(N log N). Если нам известно, что числа распределены достаточно плотно на множестве целых чисел, то можно использовать сортировку подсчётом. Тогда мы получим O(U + N), где U разность между максимальным и минимальным элементом. Случай относительно редкий, но стоит помнить про такую возможность.

После сортировки получим массив:
As = [-3, -2, -1, 3, 4, 10, 10, 15, 20, 21, 22]
Выпишем массив разностей: D = [1, 1, 4, 1, 6, 0, 5, 5, 1, 1]
Получаем, что максимальная разница составляет 6.

Теперь подумаем, можно ли решить задачу быстрее? При переборе пар соседних элементов мы будем рассматривать много пар с маленькой разностью. Такие пары, возможно, и не смогут никогда дать наибольшую разность. И действительно, в отсортированном массиве у нас есть N-1 пара соседних чисел, разность между максимумом и минимумом пусть равна U. Тогда у наибольшей разности значение должно быть хотя бы U / (N 1). Эта оценка верна по принципу Дирихле.

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


9 клеток содержат 10 голубей, по принципу Дирихле, хотя бы в одной клетке находятся более одного голубя (Википедия)

Пусть D[1] = As[2] As[1], D[n 1] = As[n] As[n 1], As[i] i-й элемент отсортированного массива. Тогда D[i] >= 0, D[1] + + D[N-1] = U.

Если предположить, что максимум из всех D[i] меньше U/(N-1), то вся сумма D[1] + + D[N-1] будет строго меньше U противоречие.

Отлично, мы получили нижнюю оценку на максимальную разность! Теперь попробуем как-то локализовать слишком близкие друг к другу элементы разобьём отрезок от минимального до максимального элемента на полуинтервалы длиной L=U/(N-1). Каждый элемент попадёт ровно в один полуинтервал. Получим разбиение всех элементов на непересекающиеся группы, их ещё называют батчами.

Определить, в какой именно из них попал элемент x, очень просто надо вычислить 1 + int((x a_min) / L) (нумеруем с единицы), где a_min минимальный элемент в массиве A, его можно найти за один проход по исходному массиву. Исключение составит только максимальный элемент элементы с таким значением лучше сделать отдельным батчом.

На рисунке представлено распределение из описанного выше примера. Для ясности проделаем эту процедуру руками:

U = 22 (-3) = 25, N = 11 => L = 25/(11-1) = 2.5
A = [-1, -3, 10, 20, 21, 4, 3, 22, 10, -2, 15]
(-1 (-3)) / 2.5 = 0.8 => batch = 1
(-3 (-3)) / 2.5 = 0 => batch = 1
(10 (-3)) / 2.5 = 13/2.5 = 5.2 =>batch = 6
(20 (-3)) / 2.5 = 23/2.5 = 9.2 => batch = 10
(21 (-3)) / 2.5 = 24/2.5 = 9.6 => batch = 10
(4 (-3)) / 2.5 = 7/2.5 = 2.8 => batch = 3
(3 (-3)) / 2.5 = 6/2.5 = 2.4 => batch = 3
(22 (-3)) / 2.5 = 10 => batch = 11
(10 (-3)) / 2.5 = 5.2 => batch = 6
(-2 (-3)) / 2.5 = 0.4 => batch = 1
(15 (-3)) / 2.5 = 7.2 => batch = 8



Распределение по батчам выполняется за линейное время и требует O(n) дополнительной памяти. Обратите внимание, что элементы из одного батча не могут дать максимальную разницу между двумя элементами. Мы специально подобрали его размер таким образом.

Где могут находиться два соседних по значениям элемента? Конечно же, в соседних непустых батчах! Например, на рисунке элементы из третьего и шестого батча могут идти подряд в отсортированном массиве, так как батчи между ними пустые. Понятно, что соседними будут только два элемента максимум из 3-го батча и минимум из 6-го. Аналогично, кандидатами на пару с максимальной разностью будут максимальный элемент первого батча и минимальный элемент третьего.

Выпишем все возможные пары элементов, которые могут дать наибольшую разность. Обозначим как min(i) минимальный элемент в i-й группе, как max(i) максимальный элемент.

max(1) = -1 min(3) = 3
max(3) = 4 min(6) = 10
max(6) = 10 min(8) = 15
max(8) = 15 min(10) = 20
max(10) = 21 min(11) = 22

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

Порадуемся мы решили задачу за время O(N).

На самом деле, мы использовали достаточно известную идею и по сути проделали первые шаги карманной сортировки, в оригинале её называют bucket-sort.


Элементы раскладываются по корзинам, а потом элементы в каждой корзине сортируются


В худшем случае она работает за O(n^2), но среднее время работы при достаточном количестве батчей и равномерном распределении элементов составляет O(n).

Но постойте, а как же случай, когда U=0, почему вы не рассмотрели его?, спросит внимательный читатель. Этот случай вырожденный, вот мы его и не рассмотрели, но давайте сделаем это сейчас для полноты вариантов.

Если разница между минимумом и максимумом равно нулю, то максимальная разница тоже равна нулю. Собственно, это всё.
Подробнее..

Из первых уст. Про впечатления от курса Яндекс Практикума Разработчик С

05.04.2021 20:15:29 | Автор: admin

Приветствую уважаемое сообщество.

В последнее время стало появляться множество курсов, связанных с IT. Вполне логично, что народ стал делиться своими наблюдениями от их прохождения. Так на Хабре можно найти отзывы об обучении на некоторых факультетах (курсах) от Яндекс Практикума [1-3]. Однако про курс "С++ разработчик" такой информации еще не было, был только рекламный пост о его запуске [4], после которого я туда и вписался, попав в первый поток (когорту).

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

О первом лице

Мне 38 лет и последние 12 лет (после защиты кандидатской) работаю в одном из научных учреждений Санкт-Петербурга. До этого успел немного поработать в IT (выездной сисадмин, установка торгового оборудования). Опыта коммерческой разработки нет. Зато есть небольшой опыт разработки программ для управления научными приборами на Delphi.

Мотивация

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

Опущу тему про почему выбор пал на Яндекс Практикум, самое интересное - что же там внутри.

Что внутри

Про тренажер, про спринты, наставников, дедлайны все уже было здесь на Хабре расписано вдоль и поперек [4-6]. Я даже догадываюсь о возможных причинах написания тех публикаций (только некоторых). Нам сейчас пообещали взамен на отзыв один из подарков на выбор. Очень хочу получить Яндекс.Носки. Не знаю пока что это и как выглядит, но представляю, что это носки из спец шерсти, которая акупунктурно воздействует на определенные точки стопы так, что в мозгу стимулируются области, ответственные за мыслительный процесс.

Как и наверно в большинстве онлайн-курсов все начинается с вводного бесплатного курса, где можно оценить свои силы, время, удобство и стиль изложения материала. Что мне больше всего понравилось, финальным проектом вводного курса был вполне законченный проект "поисковик по поиску потерянных домашних животных" с ранжированием результатов выдачи и стоп-словами. Хотя в описании вводного курса было указано ориентировочное время прохождения 24 часа, я его решал целый месяц во время отпуска. Зато на основе готового поисковика, пока ждал начала уже платного обучения, довольно легко сделал программу "Любимая фраза классиков литературы", куда вошло большинство изученных алгоритмов и приемов. Таким образом, на вводном курсе были даны базовые типы, строки, вектор, словарь, немного шаблонов и лямбда функции. Подача материала мне понравилась, она была выполнена в виде комикса с некоторой историей и юмором.

Главные герои вводного курсаГлавные герои вводного курса

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

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

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

Возможно везде так устроено, но что меня больше всего поразило, что обучение тут построено по принципу Panic Driven Education. В начале каждого нового спринта читая очередное задание впадаешь в шок от того, что это нереально сделать, тем более в такой срок. Затем начинаешь потихоньку писать код, общаешься с ребятами, с наставником, интернет в помощь и к концу спринта смотришь так со стороны - "а было совсем и не сложно". И так по нарастающей от спринта к спринту. Примерно к середине курса уже с трудом стал укладываться в срок, нагрузка возросла, задания стали на порядок сложнее. Недавно один из спринтов по просьбе большинства пришлось расширить до трех недель. Но это все общие моменты.

Что конкретно

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

Страничка моей лабораторииСтраничка моей лаборатории

По ходу обучения мы разработали свои версии стандартного вектора. Сначала детский вариант - на основе std::array, затем, вот только что, с использованием размещающего оператора new и инициализацией объектов в сырой памяти, по количеству копирований и перемещений не уступающий std::vector.

Второй крупный проект, который мы уже заканчиваем - это городской маршрутизатор. Все началось невинно, с разработки справочника остановок и маршрутов автобусов, однако очень скоро программа обросла функцией генерации карты маршрутов в SVG, работой с JSON для ввода и вывода данных, используя библиотеки которые сами же и разработали. Самая жесть была с построением оптимального маршрута между остановками с использованием графа, библиотеку которого к счастью нам уже дали готовую и надо было всего лишь разобраться с ее API. Из освоенных технологий: умные указатели, move-семантика, полиморфизм и наследование, RAII, variadic templates. Если в начале курса в тренажер для проверки приходилось загружать единственный файл main.cpp, то последний проект состоял из 24 файлов с самописными библиотеками, пространствами имен и пр.

До конца обучения остается 6 недель, из 28 студентов на нашем первом потоке осталось 12. Остальные ребята дорешивают то, что не успели с последующими потоками. Здесь допускается два таких перехода. Есть мягкие дедлайны и жесткие. Не уложился в жесткий - переходи на следующий поток.

Про проблемы тренажера

Да, было дело, в самом начале. Сделал все как в условии, задание не засчитывается, хоть ты тресни. В большинстве случаев дело было в последнем символе - либо перенос строки нужно было добавить либо пробел один убрать. Мы даже иногда угадывали. Но эти детские проблемы ко второму спринту уже починили. Единственный серьезный глюк был в недавнем задании, где тренажер слишком строго определял правильность формирования svg-файла в плане порядка тегов. Пришлось лишний день потратить на поиск возможных ошибок в своем коде, которых к счастью не оказалось. Зато нисколько не жалею, т.к. более глубоко проникся некоторыми моментами языка и оптимизировал все что можно было. Да, еще, в некоторых последних заданиях скорость работы решения проверяется наравне с корректностью алгоритма. Решать задачи становится сложнее, но интереснее.

Про портфолио

Как и было обещано, по завершению курса у каждого студента будет портфолио из решенных задач. По сути это итоговые работы каждого из спринтов. А конкретно, это многопоточный поисковый сервер, городской маршрутизатор, две реализации вектора, ну и в промежутках - реализация библиотеки для работы с json, рендеринг в svg формат, paginator и работа с графом. Возможно что-то еще вдруг неожиданно появится в оставшихсяспринтах.

Мой репозитарий с решенными задачамиМой репозитарий с решенными задачами

Про ревьюеров

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

Про нагрузку

На вводном вебинаре на мой вопрос сколько часов в день в среднем придется уделять учебе чтобы уложиться в дедлайны, я услышал ответ - порядка 4 часов в день. Реально в среднем примерно так и оказалось. Но это в среднем. Поначалу были простые задания и большинство ребят успевало отправить ревьюеру итоговые работы на неделю раньше. Как началась собственная разработка вектора, пошли итераторы, класс внутри класса, тогда все резко поменялось. Стало катастрофически не хватать времени ни на что. По пути на работу читаешь теорию и описание нового задания. В перерыве на обед делаешь наброски будущего решения, по пути домой крутишь в голове варианты реализации конкретных частей кода. Дома до часа или двух ночи отлаживаешь и тестируешь решение. И так снова и снова, пока не дойдешь до итогового задания. Т.е. телевизор, сериалы, игры можно сразу отбросить. Домашних тоже нужно морально подготовить, чтобы на 9 месяцев относились с пониманием. Не знаю, наверное можно было бы снизить нагрузку, но тогда пришлось бы растянуть программу на больший срок. Там столько всего интересного оказывается придумали в языке, что до сих пор поражаюсь А что, так можно было?.

Про трудоустройство

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

Что не хватает

Обязательной практики в реальной компании. Читая Хабр можно лишь отдаленно представить, чем занимаются начинающие разработчики в реальных компаниях. То, что есть дедлайны и спринты? Да, к этому готовят. Разбираться в чужом коде? Да, практически в каждом задании в тренажере дается код-заготовка и нужно понять и расширить его функционал. Уметь работать с GIT? Все итоговые работы мыотправляем ревьюеру через репозитарий на Github. Умение общаться с такими же новичками и опытными специалистами? Тоже да. На курсе все общение построено через Slack, все друг другу помогают советами, иногда всем миром пытаемся понять, что еще не нравится тренажеру, чтобы он засчитал задание.

Насчет типов задач, которые даются в тренажере. Практически не встречались задачи на алгоритмы, о которых недавно рассказывали тут в теме про собеседования в Яндекс [7]. Все задачи имеют адекватную связь с реальностью. Разве что при работе над темой про умные указатели мы клонировали октопусов. Знаете как называется осьминог с 4 щупальцами? Квадропус. А осьминог без щупалец? Колобок. Так авторы задания шутливо давали имена переменным в заготовке кода.

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

Личные впечатления

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

Список источников:

[1] Что вас ждет на курсе Алгоритмов в Яндекс.Практикуме

[2] Опыт обучения из первых рук. Яндекс.Практикум Аналитик данных

[3] Яндекс.Практикум Аналитик данных. Окончание обучения

[4] C++ в Практикуме. Как обучить студентов плюсам, не отпугивая

[5] Веб-тренажёр Яндекс.Практикума. Как всё устроено

[6] Что вас на самом деле ждёт на курсе про алгоритмы в Яндекс.Практикуме

[7] Собеседование в Яндекс: театр абсурда :/

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru