Algorithmics: хакаем алгоритмические собесы
1.45K subscribers
17 photos
76 links
Канал для людей, жаждущих совершенствования в мире программирования.

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

Авторы: @avivasyuta и @tifongod

Наш блог: https://algorithmics-blog.github.io/
Download Telegram
Переместите нули

Сложность: 🟢 Легкая

ℹ️ Описание

Вам дан целочисленный массив nums. Переместите в нём все нули в конец, сохраняя относительный порядок ненулевых элементов.

Обратите внимание, что вы должны сделать это in-place, не копируя массив.

⚠️ Ограничения

— Длина массива от 1 до 10000 элементов
— Каждый элемент массива может принимать значение в диапазоне от -2^31 до 2^31 - 1

1️⃣ Пример

Входные данные:


nums = [0,1,0,3,12]

Ответ


[1,3,12,0,0]

2️⃣ Пример

Входные данные:

nums = [0]

Ответ


[0]

✅ Решение

Решение задачи крайне простое.

Нам нужно завести два индекса. Первый — обычный индекс i, который инкриминируется на каждой итерации. Второй firstZeroIndex — индекс первого нулевого элемента в массиве, который изначально равен 0.

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

Таким образом все нули будут «всплывать» в конец массива за один проход..

Посмотреть реализацию в блоге

🅾️ Оценка сложности

По времени

Сложность O(n), так как мы итерируемся по всем элементам массива.

По памяти

Сложность O(1), так как мы не выделяем дополнительную память, зависящую от длины массива.

#arrays #easy
👍4🔥2❤1
Дети с наибольшим количеством конфет

Сложность: 🟢 Легкая

ℹ️ Описание

Есть n детей с конфетами.

Вам дан целочисленный массив candies, где candies[i] представляет количество конфет, которые есть у i-го ребенка, и целое число extraCandies, обозначающее количество дополнительных конфет, которые у вас есть.

Верните массив result длины n, где result[i] имеет значение true, если после предоставления i-му ребенку всех дополнительных конфет у него будет наибольшее количество конфет среди всех детей, или false в противном случае.

Обратите внимание, что несколько детей могут получить наибольшее количество конфет одновременно.

⚠️ Ограничения

— Длина массива candies находится в диапазоне от 1 до 100
— У каждого ребенка может быть не менее 1 и не более 100 конфет
— Значение extraCandies находится в диапазоне от 1 до 50

1️⃣ Пример

Входные
данные:

candies = [2,3,5,1,3]
extraCandies = 3

Ответ

[true,true,true,false,true]

2️⃣ Пример

Входные дан
ные:

candies = [4,2,1,1,2]
extraCandies = 1

Ответ


[true,false,false,false,false]

✅ Решение

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

Посмотреть реализацию в блоге

🅾️ Оценка сложности

По времени

Для того чтобы найти ответ, мы дважды итерируемся по массиву, то есть совершаем 2n операций. Итоговая сложность алгоритма равна O(n).

По памяти

Сложность по памяти линейная O(n), так как мы создаем массив длиной n для хранения ответа.

#arrays #easy
❤1🔥1👏1
Определите, близки ли две строки

Сложность: 🟡 Средняя

ℹ️ Описание

Вам дано две строки. Верните true, если обе строки являются близкими, и false в противном случае.

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

Операция 1: Поменяйте местами любые два существующих символа (swap). Например, abcde -> aecdb (букву b поменяли местами с буквой e).

Операция 2: Преобразуйте каждое появление одного существующего символа в другой существующий символ и сделайте то же самое с другим символом. Например, aacabb -> bbcbaa (все буквы a превращаются в буквы b, а все буквы b превращаются в буквы a).

Вы можете использовать операции с любой строкой столько раз, сколько необходимо.

⚠️ Ограничения

— Длина каждого слова находится в диапазоне от 1 до 10^5
— Оба слова содержат только строчные буквы латинского алфавита

1️⃣ Пример

Входные данные:
word1 = "abc", word2 = "bca"

Ответ:
true

Объяснение

Вы можете получить word1 из word2 за 2 операции.

Примените операцию 1: «abc» -> «acb»
Примените операцию 1: «acb» -> «bca»

2️⃣ Пример

Входные данные:
word1 = "a", word2 = "aa"

Ответ:
false

Объяснение

Невозможно получить word2 из word1 или наоборот за любое количество операций.

3️⃣ Пример

Входные данные:
word1 = "cabbba", word2 = "abbccc"

Ответ:
true

Объяснение

Вы можете получить word1 из `word2 за 3 операции.

Примените операцию 1: «cabbba» -> «caabbb»
Примените операцию 2: «caabbb» -> «baaccc»
Примените операцию 2: «baaccc» -> «abbccc»

✅ Решение

Посмотреть решение в блоге

#strings #medium
🔥6😁2❤1👏1🤡1
Разворот связанного списка

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

Сложность: 🟢 Легкая

ℹ️ Описание

Вам дан односвязный список. Переверните список и верните его.

Список представлен следующей структурой.


class ListNode {
val: number
next: ListNode | null
}


⚠️ Ограничения

— Количество узлов в связанном списке находится в диапазоне от 1 до 5000
— Значение каждого узла находится в диапазоне от -5000 до 5000

✅ Решение

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

Посмотреть реализацию в блоге

🅾️ Оценка сложности

По времени

Сложность O(n), так как мы итерируемся по всем узлам списка.

По памяти

Сложность O(1), так как мы не выделяем дополнительную память.

#linked_list #easy
👍4🔥2❤1
Является ли строка подпоследовательностью

Сложность: 🟢 Легкая

ℹ️ Описание

Дано две строки s и t. Напишите функцию, которая возвращает true, если s является подпоследовательностью t, или false в противном случае.

Подпоследовательность строки — это новая строка, которая формируется из исходной строки путем удаления некоторых (может быть ни одного) символов без нарушения относительного положения остальных символов. (т. е. «ace» является подпоследовательностью abcde, а «aec» — нет).

⚠️ Ограничения

— Длина строки s находится в диапазоне от 0 до 100
— Длина строки t находится в диапазоне от 0 до 10000
— В строках могут присутствовать только латинские буквы в нижнем регистре

1️⃣ Пример

Входные данные



s = "abc"
t = "ahbgdc"


Ответ



true


2️⃣ Пример

Входные данные



s = "axc"
t = "ahbgdc"


Ответ



false



✅ Решение

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

Мы запускаем цикл по всем символам строки t и сравниваем их с символами строки s. Для отслеживания позиции в строке s мы будем использовать указатель pos.

- Если символы совпадают, мы двигаем указатель строки pos на одну позицию вперед.
- Если после прохода по всем символам строки t указатель pos указывает на конец строки s, значит строка s является подпоследовательностью строки t.
- Если во время очередной итерации выполняется это же условие, то мы моем досрочно завершить выполнение функции и вернуть true, так как дальнейший проход по строке t не имеет смысла.

Посмотреть реализацию в блоге

#strings #easy
❤4👍3🔥3
Привет, друзья. Простите нас за молчание, просто админы ушли в отпуск 😄

Через неделю мы вернемся к вам с новыми задачами, а пока что вы можете следить за нашими приключениями в Намибии в этом канале.
🔥4
Сжатие строки

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

Сложность: 🟡 Средняя

ℹ️ Описание

Дана строка в виде массива символов. Необходимо написать функцию, которая сожмет входящий массив и вернет количество символов в сжатом массиве по следующему принципу:
— Если символ повторяется больше одного раза подряд, нужно заменить всю подстроку на строку ['a', 'n'], где a - исходный символ, n - количество повторений этого символа, идущих подряд. В случае если n — многозначное число, каждая цифра должна быть добавлена отдельным символом.
— Если буква не повторяется - оставить ее без изменений.

Все изменения нужно совершить in-place, в качестве ответа функции вернуть количество символов в сжатой строке.

⚠️ Ограничения

— Длина входящего массива от 1 до 2000 символов
— Элементы массива - символы латиницы, цифры или знаки

1️⃣ Пример

Входные данные



chars = ["a","a","b","b","c","c","c"]


Ответ



6


Исходный массив должен быть преобразован в


chars = ["a","2","b","2","c","3"]


2️⃣ Пример

Входные данные



chars = ["a"]


Ответ



1


3️⃣ Пример

Входные данные



chars = ["a","b","b","b","b","b","b","b","b","b","b","b","b"]


Ответ



4


Исходный массив должен быть преобразован в


chars = ["a","b","1","2"]


✅ Решение

Задача решается достаточно элементарно, главная загвоздка - замена элементов in-place.

Для решение задачи нам понадобятся несколько индексов:
— Индекс текущего элемента lastElemIdx
— Индекс начала последовательности одинаковых элементов firstElemIdx
— Индекс элемента, который будет заменен при сжатии строки newPositionIdx. Для эффективности решения мы будем заменять элементы исходного массива, а после «отрежем» хвост. В противном случае нам бы пришлось вырезать повторяющиеся символы, смещая хвост массива на n элементов влево, что значительно замедлит алгоритм.

Далее нам достаточно аккуратно обойти и модифицировать исходный массив.

Посмотреть реализацию в блоге

#arrays #medium
🔥3👍2💅1
Максимальное количество пар K-суммы

Сложность: 🟡 Средняя

ℹ️ Описание

Вам дан целочисленный массив nums и целое число k.

За одну операцию вы можете выбрать из массива два числа, сумма которых равна k, и удалить их из массива.

Верните максимальное количество операций, которые вы можете выполнить с массивом.

⚠️ Ограничения

— Длина массива находится в диапазоне от 1 до 10^5
— Каждый элемент массива может принимать значение в диапазоне от 1 до 10^9
— k находится в диапазоне от 1 до 10^9

1️⃣ Пример

Входные данные


nums = [1,2,3,4], k = 5

Ответ
: 2

Вы можете удалить две пары чисел: (1,4) и (2,3), сумма которых равна 5.

2️⃣ Пример

Входные данные


nums = [3,1,3,4,3], k = 6

Ответ
: 1

Вы можете удалить одну пару чисел: (3,3), сумма которых равна 6.


✅ Решение

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

Запускаем цикл по всем элементам массива и для каждого числа рассчитываем разность diff между k и текущим числом num.

Если в объекте digits уже есть запись для diff, это означает, что ранее было найдено число, которое в сумме с текущим числом num даст k. Если это так, то уменьшаем количество доступных чисел diff на единицу, и счетчик пар res увеличивается на один, так как найдена новая пара.

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

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

Посмотреть реализацию в блоге

#arrays #medium
👍3🔥3❤2
Максимальное количество гласных в подстроке заданного размера

Сложность: 🟡 Средняя

ℹ️ Описание

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

⚠️ Ограничения

— Длина строки от 1 до 100000
— Строка состоит только из латинских букв в нижнем регистре
— Размер подстроки находится в диапазоне от 1 до длины исходной строки s.

1️⃣ Пример

Входные данные



s = "abciiidef"
k = 3


Ответ



3


В исходной строке есть подстрока длиной 3 состоящая исключительно из гласных iii.

2️⃣ Пример

Входные данные



s = "aeiou"
k = 2


Ответ



2


Так как исходная строка состоит полностью из гласных, любая подстрока длиной 2 также будет состоять из гласных.

3️⃣ Пример

Входные данные



s = "leetcode"
k = 3


Ответ



2


В исходной строке есть несколько подстрок (`lee`/`eet`/`ode`), в любой из которых максимальное количество гласных равно 2.

✅ Решение

Данная задача является ярким представителем класса задач, которые решаются с помощью скользящего окна:
- Нам необходимо создать окно длиной k (исходно левая граница окна равна 0, а правая - `k-1`)
- Двигать наше окно увеличивая левый и правый индекс на 1 на каждом шаге
- Считать сколько гласных попадает в наше окно на каждом шаге

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

Посмотреть реализацию в блоге

#strings #medium
👍3🔥1
Максимальный средний подмассив

Сложность: 🟢 Легкая

ℹ️ Описание

Вам дан целочисленный массив nums, состоящий из n элементов, и целое число k. Найдите непрерывный подмассив длиной k, имеющий максимальное среднее значение, и верните это значение.

Принимается любой ответ с ошибкой расчета менее 10^-5.

⚠️ Ограничения

— k гарантированно меньше или равно длине массива nums
— Количество элементов в массиве может быть в диапазоне от 1 до 10^5
— Каждый элемент массива может принимать значение в диапазоне от -10^4 до 10^4

1️⃣ Пример

Входные данные


nums = [1,12,-5,-6,50,3]
k = 4

Ответ


12.75

2️⃣ Пример

Входные данные


nums = [5]
k = 1

Ответ


5

✅ Решение

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

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

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

Таким образом, после завершения всех итераций по массиву, в переменной maxSum будет храниться максимальное значение суммы подмассива длиною k. В самом конце только остается поделить эту сумму на k таким образом получив среднее значение подмассива.

Посмотреть реализацию в блоге

#arrays #easy
👍3🔥1😱1
Сколько нужно стрел, чтобы лопнуть все воздушные шары

Сложность: 🟡 Средняя

ℹ️ Описание

К плоской стене, представляющей плоскость XY, приклеено несколько сферических воздушных шаров. Шары представлены в виде двумерного целочисленного массива points, где points[i] = [xstart, xend] обозначает шар, горизонтальный диаметр которой простирается между xstart и xend. Вы не знаете точных координат Y воздушных шаров.

Вы можете выстрелить стрелой прямо вертикально (в положительном направлении Y) из разных точек вдоль оси X. Воздушный шар с xstart, xend разрывается стрелой, выпущенной в x, если x start <= x <= xend. Нет ограничений на количество выпущенных стрел.

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

⚠️ Ограничения

— В массиве points может быть от 1 до 105 элементов
— 2^31 <= xstart < xend <= 2^31 - 1

1️⃣ Пример

Входные данные


points = [[10,16],[2,8],[1,6],[7,12]]

Ответ
: 2

Пояснение

— Выстрелите стрелой в точку x = 6, лопнув шарики [2, 8] и [1, 6].
— Выстрелите стрелой в точку x = 11, лопнув шарики [10, 16] и [7, 12].

2️⃣ Пример

Входные данные


points = [[1,2],[3,4],[5,6],[7,8]]

Ответ
: 4

Пояснение

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

3️⃣ Пример

Входные данные


points = [[1,2],[2,3],[3,4],[4,5]]

Ответ
: 2

Пояснение

— Выстрелите стрелой в точку x = 2, лопнув шарики [1, 2] и [2, 3].
— Выстрелите стрелой в точку x = 4, лопнув шарики [3, 4] и [4, 5].

✅ Решение

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

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

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

Исходя из этого мы можем сформировать крайне простой алгоритм:

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

Посмотреть реализацию в блоге

#arrays #medium
👍2🔥2❤1
Привет. Сегодня я бы хотел обсудить с вами один интересный вопрос, который не касается алгоритмов, но напрямую относится к работе.

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

В чем суть. Есть условно два стула.

1. Одни считают, что писать нормально в любое момент. Человек с передающей стороны может написать сообщение в удобное ему время. Человек же с принимающей стороны сам может выстроить свой режим и окружение таким образом, чтобы получать уведомления и отвечать на них только в рабочее время. То есть ответственность за обеспечение своего удобства и комфорта на принимающей стороне.

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

Расскажите, что вы думаете по этому поводу? Какой вариант для вас более предпочтительный? Или может у вас есть другие идеи?
👍2🕊1
Стек наиболее часто встречающихся элементов

Всем привет!
Мы решили попробовать разбавить легкие и средние задачи — сложными 🙂
На самом деле эта задача лежала у меня в черновиках довольно долго. Настолько, что я успел несколько раз забыть как работает куча, а также за это время мы успели завести этот канал и несколько раз обновить блог (кстати, зацените новый формат. Кажется, стало немного симпатичнее).

Итак, задача по реализации модифицированного стека: который позволяет сперва доставать наиболее частотные элементы, а в случае наличия групп элементов с одинаковой частотой - работать как классический стек между этими группами.
На самом деле, наиболее приближенная задача из практических — Priority queue. Только в нашем случае вместо приоритетов — частоты, а вместо алгоритма FIFO - LIFO.

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

Сложность: 🤬 Сложная

ℹ️ Описание

Необходимо реализовать структуру данных, которая позволит сохранять и доставать целочисленные элементы. Для этого необходимо реализовать 3 функции:
1. Constructor() FreqStack конструктор, инициализирующий структуры данных.
2. FreqStack.Push(val int) метод структуры данных, позволяющий сохранить целочисленный элемент внутрь структуры данных FreqStack
3. FreqStack.Pop() int метод структуры данных, удаляющий и возвращающий элемент из структуры данных. Сперва извлекаются наиболее частотные элементы. При наличии групп эллементов, имеющих одинаковую частоту, извлекать элементы из групп с одинаковой частотой по принципу стека (LIFO)

⚠️ Ограничения

— Добавляемые/извлекаемые элементы лежат в диапазоне от 0 до 1 000 000 000
— Максимальное количество вызовов функций Push и Pop - 20 000
— Гарантируется, что перед вызовом функции Pop в FreqStack будет как минимум один элемент

1️⃣ Пример



stack := Constructor()

stack.Push(5)
stack.Push(7)
stack.Push(5)
stack.Push(7)
stack.Push(4)
stack.Push(5)


Out:

stack.Pop() // 5
stack.Pop() // 7
stack.Pop() // 5
stack.Pop() // 4


Пояснение

— Первым извлекается элемент 5, так как он самый частотный в стеке. При этом извлекается та 5, которая была добавлена последней (по принципу стека)
— Вторым извлекается элемент 7, так как в стеке оба элемента 5 и 7 встречаются по 2 раза, но последняя 7 была добавлена позже, чем последняя 5 (5, которую мы добавили последним Push'ом мы извлекли первым вызовом Pop)
— Третьим элементом извлекается элемент 5, так как теперь это самый частотный элемент в стеке
— Четвертым извлекается элемент 4, так как теперь в стеке все элементы имеют одинаковую частоту (каждый элемент встречается один раз), а элемент 4 был добавлен в стек последним


✅ Решение

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

Посмотреть реализацию и объяснения в блоге.

#heap #hard
🔥6❤2👍2
Минимальное количество переворотов, чтобы сделать A | B == C

Привет, друзья.

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

Сложность: 🟡 Средняя

ℹ️ Описание

Даны три целых положительных числа a, b и c.

Необходимо найти минимальное количество переворотов битов в a и b, чтобы результат операции a OR b (побитовая операция ИЛИ) был равен c. Операция переворота состоит из изменения любого отдельного бита с 1 на 0 или c 0 на 1 в его двоичном представлении.

⚠️ Ограничения

Значение каждого аргумента находится в диапазоне от 1 до 10^9

1️⃣ Пример

Входные данные: a = 2, b = 6, c = 5

Ответ: 3


2️⃣ Пример

Входные данные: a = 4, b = 2, c = 7

Ответ: 1


3️⃣ Пример

Входные данные: a = 1, b = 2, c = 3

Ответ: 0


✅ Решение

Чтобы решить задачу, нам необходимо представить числа a, b и c в двоичном виде и произвести их сравнение побитно. Недостающие биты в числах мы заполняем ведущими нулями.

Если бит числа c равен 1, это означает, что в a и b как минимум один бит на этой же позиции должен быть равен 1, чтобы условие a | b == c было истинным. Если же оба бита в a и b равны 0, то нам необходимо изменить любой из битов на единицу. Не важно какой именно бит, потому что это не меняет результат.

Если бит числа c равен 0, это означает, что биты и в числе a, и в числе b должны быть равны нулю, чтобы условие a | b == c было истинным. В этом случае нам нужно изменять биты в a и b только в том случае, если они равны 1.

❓ Как получить двоичное представление числа ❓

На самом деле число не нужно переводить в двоичную систему счисления. Достаточно воспользоваться небольшим математическим хаком.

- Чтобы получить младший бит числа в двоичном представлении (крайний справа) необходимо взять остаток от деления числа на 2. Это работает потому что у четных чисел младший бит всегда равен 0, а у нечетных — 1.

- Чтобы откинуть младший бит у числа достаточно разделить его на 2 без остатка. В таком случае мы получим новое число, которое в двоичном представлении равно предыдущему числу без младшего бита.

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

Посмотреть реализацию в блоге

#bit_manipulation #medium
❤2👍2🔥2
Привет, друзья!

Нам очень важно делать разбор задач так, чтобы это было максимально удобно и понятно для вас. Поделитесь, какой формат вам больше подходит и нравится?
Anonymous Poll
38%
Подробный разбор задач в телеге
24%
Подробный разбор задач в блоге, тут хватает анонса
16%
Оставьте как есть
21%
Какой блог? 🤨
0%
Свой вариант в комментах
Вижу, многие не знают о том, что у нас есть блог.

Здесь мы выкладываем подробные разборы всех задач в удобном формате, чтобы вы могли без проблем прочитать все содержимое и удобно смотреть на код решения.
👍8❤2🔥2✍1🤔1
Пары одинаковых строк и колонок

Сложность: 🟡 Средняя

ℹ️ Описание

Дана матрица grid размером n x n, которая состоит из целых положительных чисел.

Посчитайте количество пар (r[i], c[j]), где строка r[i] равна колонке c[j]

Строка и колонка считаются равными, если они состоят из одинаковых элементов в одинаковом порядке.

⚠️ Ограничения

— Размер n находится в диапазоне от 1 до 200
— Значение каждого элемента в матрице находится в диапазоне от 1 до 10^5

1️⃣ Пример

Входные данные: grid = [[3,2,1],[1,7,6],[2,7,7]]

Ответ:
1

Есть одна одинаковая пара:
— (Строка 2, Колонка 1): [2,7,7]

2️⃣ Пример

Входные данные: grid = [[3,1,2,2],[1,4,4,5],[2,4,2,2],[2,4,2,2]]

Ответ:
3

Есть три одинаковые пары:
— (Строка 0, Колонка 0): [3,1,2,2]
— (Строка 2, Колонка 2): [2,4,2,2]
— (Строка 3, Колонка 2): [2,4,2,2]

✅ Решение

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

Создаем карту подсчета строк:

Инициализируем пустую хеш-таблицу (объект в случае TypeScript) countMap, который будет хранить строки в виде ключей и их количество в виде значений.
Проходим по каждой строке матрицы. Для каждой строки создаем строковой ключ, конкатенируя все её элементы, разделенные точкой с запятой.
Если такой ключ уже существует в countMap, увеличиваем значение на единицу. В противном случае, устанавливаем значение равным единице.
Подсчет совпадающих столбцов:

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

Посмотреть реализацию в блоге

#matrix #hash_table #medium
🔥4❤2👍2
Максимальная глубина бинарного дерева

Сложность: 🟢 Легкая

ℹ️ Описание

Найдите максимальную глубину бинарного дерева.

Максимальная глубина бинарного дерева — это количество узлов на самом длинном пути от корневого узла до самого дальнего листового узла.

⚠️ Ограничения

— Количество узлов в дереве может быть от 0 до 10000
— Значение каждого узла находится в диапазоне от -100 до 100

✅ Решение

Для решения задачи нам достаточно реализовать простой поиск в глубину DFS (Depth First Search).

Реализуем дополнительную рекурсивную функцию dfs, которая принимает на вход два аргумента:
— текущий узел дерева node
— текущую глубину погружения level

В качестве ответа функция будет возвращать максимальную глубину ветки дерева.

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

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

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

Посмотреть реализацию в блоге

#tree #binary_tree #easy
🔥3
Листоподобные деревья

Сложность: 🟢 Легкая

ℹ️ Описание

Дано два бинарных дерева. Все листовые узлы дерева образуют последовательность значений.
Например, для дерева на картинке последовательность значений будет равна (6, 7, 4, 9, 8).

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

Напишите функцию, которая возвращает true тогда и только тогда, когда два заданных дерева являются листоподобными.

⚠️ Ограничения

— Количество узлов в каждом дереве находится в диапазоне [1, 200]
— Узлы обоих деревьев имеют значения в диапазоне [0, 200]

✅ Решение

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

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

Функция осуществляет классический обход дерева в глубину DFS (Depth First Search) и когда встречает листовой узел (узел, у которого нет потомков), то добавляет его значение в строку sequence, разделяя значения точкой с запятой. Предварительно мы определяем переменную sequence, равную пустой строке, и управляем ее значением через замыкание.

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

Посмотреть реализацию в блоге

#tree #binary_tree #easy
🔥5👍2❤1🎄1👾1
Всем привет!

У меня есть небольшое хобби - я люблю коллекционировать профессиональную литературу. Громко сказано, на пара полок с Таненбаумом, «Философией Java» и прочими томами служили украшением моего домашнего рабочего места, пока я не перешел на кочевой образ жизни 🙂 Правда, если быть честным, большинство книг я прочел едва ли наполовину, но все же 🙂

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

Начну с себя: одна из лучших книг в моей коллекции — «Designing Data-Intensive Applications» Мартина Клеппмана (она же всем известная «книжка с кабанчиком», она же «Высоконагруженные приложения. Программирование, масштабирование, поддержка»).

Однозначно рекомендую к прочтению. Конечно, одна эта книга не позволит вам с нуля разработать свою распределённую базу данных, но она поможет углубиться в проблематику работы с данными и осознать, что никаких гарантий не существует (особенно в распределённых системах) и насколько сложно и дорого поддерживать хотя бы иллюзию этих гарантий.
👍14🔥4👨‍💻2💅2🤔1
Самая длинная подстрока, являющаяся валидной скобочной последовательностью

Всем привет!
Сегодня продолжаем одновременно 2 начинания: решать сложные задачи и решать задачи на валидные скобочные последовательности 🙂

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

Но, на самом деле, эта задача не из простых и, стоит хоть немного отойти от классической формулировки, и из легкой она превращается в сложную. Так и в сегодняшней вариации — даже несмотря на то, что оптимальное решение использует все тот же самый стек (да и алгоритм в итоге очень несложный), догадался я до него не с первого раза.

Сложность: 🤬 Сложная

ℹ️ Описание

Напишите функцию для поиска самой длинной подстроки, являющейся правильной скобочной последовательностью.

⚠️ Ограничения

— Длина каждой строки от 0 до 30 000 символов
— Строка состоит из символов '(' и ')'

1️⃣ Пример
Входные данные


s := "(()"

Ответ


2


Самая длинная правильная скобочная последовательность — ().

2️⃣ Пример
Входные данные


s := ")()())"

Ответ


4


Самая длинная правильная скобочная последовательность - ()().

3️⃣ Пример
Входные данные


s := ""

Ответ


0


✅ Решение

Можно попробовать пойти с помощью брутфорса — взять всю строку (как самую длинную возможную подстроку) и проверить её на валидность. После уменьшить длину подстроки на единицу и проверить на валидность 2 возможные подстроки длиной n-1. Потом ещё на единицу и проверить три возможные подстроки длиной n-2. И так далее, пока не наткнёмся на первую валидную подстроку. Но такое решение будет слишком долгим — на LeetCode вы даже не сможете пройти все тест-кейсы из-за таймаута.

Чтобы найти более оптимальное решение, можно переформулировать задачу: самая длинная подстрока с валидной скобочной последовательностью — это максимальное расстояние между двумя невалидными подстроками. Осталось придумать, как с помощью стека мы можем вырезать все валидные подстроки из оригинальной строки, и задача будет решена 🙂

Посмотреть реализацию и объяснения в блоге.

#stack #hard
🔥5👀2