Kotlin | LeetCode
1.84K subscribers
167 photos
1.1K links
Cайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp

Тесты t.iss.one/+Gzg9SH2MNxM0ZTYy
Вопросы соебсов t.iss.one/+OOb6zFa_-Oo3NjZi
Вакансии t.iss.one/+KuGNaHeKkQg1NzAy
Download Telegram
Задача: 568. Maximum Vacation Days
Сложность: hard

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

Правила и ограничения:
Вы можете путешествовать только между n городами, обозначенными индексами от 0 до n-1. Изначально вы находитесь в городе с индексом 0 в понедельник.
Города связаны рейсами. Рейсы представлены матрицей n x n, называемой flights, представляющей статус авиалинии от города i до города j. Если рейса из города i в город j нет, flights[i][j] == 0; иначе flights[i][j] == 1. Также для всех i выполняется flights[i][i] == 0.
У вас есть k недель (каждая неделя состоит из семи дней) для путешествий. Вы можете летать не более одного раза в день и можете летать только утром каждого понедельника. Время полета настолько короткое, что его влияние не учитывается.
Для каждого города у вас есть ограниченные дни отпуска в разные недели, заданные матрицей n x k, называемой days. Значение days[i][j] представляет максимальное количество дней отпуска, которые вы можете взять в городе i на неделе j.
Вы можете оставаться в городе большее количество дней, чем количество дней отпуска, но вы должны работать в дополнительные дни, которые не будут учитываться как дни отпуска.
Даны две матрицы flights и days, верните максимальное количество дней отпуска, которые вы можете взять в течение k недель.

Пример:
Input: flights = [[0,1,1],[1,0,1],[1,1,0]], days = [[1,3,1],[6,0,3],[3,3,3]]
Output: 12
Explanation:
One of the best strategies is:
1st week : fly from city 0 to city 1 on Monday, and play 6 days and work 1 day.
(Although you start at city 0, we could also fly to and start at other cities since it is Monday.)
2nd week : fly from city 1 to city 2 on Monday, and play 3 days and work 4 days.
3rd week : stay at city 2, and play 3 days and work 4 days.
Ans = 6 + 3 + 3 = 12.


👨‍💻 Алгоритм:

1⃣Использовать функцию dfs (поиск в глубину), которая возвращает количество отпускных дней, которые можно взять, начиная с текущего города cur_city и текущей недели weekno. В каждом вызове функции проходить по всем городам и находить все города, которые связаны с текущим городом. Такой город обозначен 1 в соответствующей позиции flights[cur_city][i].

2⃣Для текущего города можно либо остаться в нем, либо поехать в связанный город. Обозначим город, в который меняется расположение, как j. После смены города нужно найти количество отпускных дней, которые можно взять, начиная с нового города и с новой недели. Это количество отпускных дней можно представить как: days[j][weekno] + dfs(flights, days, j, weekno + 1).

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

😎 Решение:
class Solution {
fun maxVacationDays(flights: Array<IntArray>, days: Array<IntArray>): Int {
val n = flights.size
val k = days[0].size
val memo = Array(n) { IntArray(k) { -1 } }

fun dfs(curCity: Int, weekNo: Int): Int {
if (weekNo == k) return 0
if (memo[curCity][weekNo] != -1) return memo[curCity][weekNo]
var maxVac = 0
for (nextCity in 0 until n) {
if (curCity == nextCity || flights[curCity][nextCity] == 1) {
maxVac = maxOf(maxVac, days[nextCity][weekNo] + dfs(nextCity, weekNo + 1))
}
}
memo[curCity][weekNo] = maxVac
return maxVac
}

return dfs(0, 0)
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1122. Relative Sort Array
Сложность: easy

Даны два массива arr1 и arr2, элементы arr2 уникальны, и все элементы arr2 также присутствуют в arr1.

Отсортируйте элементы arr1 таким образом, чтобы относительный порядок элементов в arr1 был таким же, как в arr2. Элементы, которые не встречаются в arr2, должны быть размещены в конце arr1 в порядке возрастания.

Пример:
Input: arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
Output: [2,2,2,1,4,3,3,9,6,7,19]


👨‍💻 Алгоритм:

1⃣Инициализация и подсчёт:
Инициализируйте пустой массив result и массив remaining для хранения оставшихся элементов.
Создайте хеш-таблицу countMap для хранения количества вхождений каждого элемента из arr2 в arr1.

2⃣Заполнение countMap и remaining:
Пройдитесь по элементам arr1 и если элемент присутствует в countMap, увеличьте его счетчик. Если элемент не присутствует в arr2, добавьте его в remaining.

3⃣Формирование результирующего массива:
Пройдитесь по arr2 и добавьте элементы в result в соответствии с их количеством в countMap.
Отсортируйте массив remaining и добавьте его элементы в result.
Верните result в виде массива.

😎 Решение:
class Solution {
fun relativeSortArray(arr1: IntArray, arr2: IntArray): IntArray {
val countMap = mutableMapOf<Int, Int>()
val remaining = mutableListOf<Int>()
val result = mutableListOf<Int>()

for (value in arr2) {
countMap[value] = 0
}

for (value in arr1) {
if (value in countMap) {
countMap[value] = countMap[value]!! + 1
} else {
remaining.add(value)
}
}

remaining.sort()

for (value in arr2) {
repeat(countMap[value]!!) {
result.add(value)
}
}

result.addAll(remaining)
return result.toIntArray()
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 496. Next Greater Element I
Сложность: easy

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

Вам даны два различных целочисленных массива с индексами, начинающимися с 0: nums1 и nums2, где nums1 является подмножеством nums2.
Для каждого 0 <= i < nums1.length найдите индекс j, такой что nums1[i] == nums2[j], и определите следующий больший элемент для nums2[j] в nums2. Если следующего большего элемента нет, то ответ для этого запроса — -1.

Верните массив ans длиной nums1.length, где ans[i] — это следующий больший элемент, как описано выше.

Пример:
Input: nums1 = [4,1,2], nums2 = [1,3,4,2]
Output: [-1,3,-1]
Explanation: The next greater element for each value of nums1 is as follows:
- 4 is underlined in nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1.
- 1 is underlined in nums2 = [1,3,4,2]. The next greater element is 3.
- 2 is underlined in nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1.


👨‍💻 Алгоритм:

1⃣Инициализация и поиск совпадений
Создайте массив res для хранения результатов. Для каждого элемента nums1[i] найдите его индекс j в массиве nums2.

2⃣Поиск следующего большего элемента
После нахождения индекса j в nums2 начните поиск элемента справа от nums2[j], который больше nums1[i]. Если такой элемент найден, добавьте его в res.

3⃣Заполнение результата
Если следующий больший элемент не найден, добавьте -1 в соответствующую позицию res. Верните массив res.

😎 Решение:
class Solution {
fun nextGreaterElement(nums1: IntArray, nums2: IntArray): IntArray {
val res = IntArray(nums1.size) { -1 }

for (i in nums1.indices) {
var found = false
for (j in nums2.indices) {
if (nums2[j] == nums1[i]) {
found = true
}
if (found && nums2[j] > nums1[i]) {
res[i] = nums2[j]
break
}
}
}

return res
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 654. Maximum Binary Tree
Сложность: medium

Вам дан целочисленный массив nums без дубликатов. Из nums можно рекурсивно построить максимальное двоичное дерево, используя следующий алгоритм: создайте корневой узел, значение которого равно максимальному значению в nums. Рекурсивно постройте левое поддерево по префиксу подмассива слева от максимального значения. Рекурсивно постройте правое поддерево по суффиксу подмассива справа от максимального значения. Верните максимальное двоичное дерево, построенное из nums.

Пример:
Input: nums = [3,2,1,6,0,5]
Output: [6,3,5,null,2,0,null,null,1]


👨‍💻 Алгоритм:

1⃣Найдите максимальное значение в текущем подмассиве и создайте узел с этим значением.

2⃣Рекурсивно постройте левое поддерево для подмассива слева от максимального значения.

3⃣Рекурсивно постройте правое поддерево для подмассива справа от максимального значения.

😎 Решение:
class TreeNode(var `val`: Int) {
var left: TreeNode? = null
var right: TreeNode? = null
}

fun constructMaximumBinaryTree(nums: IntArray): TreeNode? {
return build(nums, 0, nums.size)
}

private fun build(nums: IntArray, l: Int, r: Int): TreeNode? {
if (l == r) return null
val maxIndex = (l until r).maxByOrNull { nums[it] } ?: l
val root = TreeNode(nums[maxIndex])
root.left = build(nums, l, maxIndex)
root.right = build(nums, maxIndex + 1, r)
return root
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1008. Construct Binary Search Tree from Preorder Traversal
Сложность: medium

Дается массив целых чисел preorder, который представляет собой обход BST (т.е, гарантируется, что для заданных тестовых случаев всегда можно найти дерево двоичного поиска с заданными требованиями. Дерево двоичного поиска - это двоичное дерево, в котором для каждого узла любой потомок Node.left имеет значение строго меньше, чем Node.val, а любой потомок Node.right имеет значение строго больше, чем Node.val. При обходе бинарного дерева в предварительном порядке сначала выводится значение узла, затем обход Node.left, затем обход Node.right.

Пример:
Input: preorder = [8,5,1,7,10,12]
Output: [8,5,10,1,7,null,12]


👨‍💻 Алгоритм:

1⃣Инициализация переменных и функций:
Создайте класс узла дерева TreeNode с атрибутами val, left и right.
Инициализируйте индекс, который будет отслеживать текущую позицию в массиве preorder.

2⃣Рекурсивная функция для построения дерева:
Создайте рекурсивную функцию constructBST с аргументами preorder, lower и upper, которые будут ограничивать значения узлов для текущей ветви дерева.
Если текущий индекс выходит за границы массива preorder, верните null.
Получите текущее значение из preorder и проверьте, находится ли оно в пределах допустимого диапазона [lower, upper]. Если нет, верните null.

3⃣Создание узла и рекурсивное построение поддеревьев:
Создайте новый узел с текущим значением и увеличьте индекс.
Рекурсивно вызовите функцию constructBST для создания левого поддерева с обновленным верхним пределом (upper равным значению текущего узла).
Рекурсивно вызовите функцию constructBST для создания правого поддерева с обновленным нижним пределом (lower равным значению текущего узла).
Верните созданный узел.

😎 Решение:
class TreeNode(var `val`: Int) {
var left: TreeNode? = null
var right: TreeNode? = null
}

class Solution {
private var index = 0

fun bstFromPreorder(preorder: IntArray): TreeNode? {
return constructBST(preorder, Int.MIN_VALUE, Int.MAX_VALUE)
}

private fun constructBST(preorder: IntArray, lower: Int, upper: Int): TreeNode? {
if (index == preorder.size) return null

val value = preorder[index]
if (value < lower || value > upper) return null

index++
val root = TreeNode(value)
root.left = constructBST(preorder, lower, value)
root.right = constructBST(preorder, value, upper)
return root
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 743. Network Delay Time
Сложность: medium

Дана сеть из узлов, помеченных от 1 до n. Также дано times - список времен прохождения сигнала в виде направленных ребер times[i] = (ui, vi, wi), где ui - исходный узел, vi - целевой узел, а wi - время прохождения сигнала от источника до цели. Мы пошлем сигнал из заданного узла k. Верните минимальное время, которое потребуется всем узлам, чтобы получить сигнал. Если все узлы не могут получить сигнал, верните -1.

Пример:
Input: times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
Output: 2


👨‍💻 Алгоритм:

1⃣Представьте граф в виде списка смежности.

2⃣Используйте алгоритм Дейкстры для нахождения кратчайших путей от узла k до всех других узлов.

3⃣Найдите максимальное значение среди кратчайших путей к узлам. Если какой-либо узел недостижим, верните -1.

😎 Решение:
import java.util.*

fun networkDelayTime(times: Array<IntArray>, n: Int, k: Int): Int {
val graph = mutableMapOf<Int, MutableList<Pair<Int, Int>>>()
for (i in 1..n) {
graph[i] = mutableListOf()
}
for (time in times) {
graph[time[0]]?.add(Pair(time[1], time[2]))
}

val minHeap = PriorityQueue<Pair<Int, Int>>(compareBy { it.first })
minHeap.add(Pair(0, k))
val minTime = mutableMapOf<Int, Int>()
for (i in 1..n) {
minTime[i] = Int.MAX_VALUE
}
minTime[k] = 0

while (minHeap.isNotEmpty()) {
val (time, node) = minHeap.poll()
graph[node]?.forEach { (neighbor, t) ->
val newTime = time + t
if (newTime < minTime[neighbor]!!) {
minTime[neighbor] = newTime
minHeap.add(Pair(newTime, neighbor))
}
}
}

val maxTime = minTime.values.maxOrNull()!!
return if (maxTime == Int.MAX_VALUE) -1 else maxTime
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 600. Non-negative Integers without Consecutive Ones
Сложность: Hard

Дано положительное целое число n, верните количество чисел в диапазоне [0, n], бинарные представления которых не содержат последовательных единиц.

Пример:
Input: n = 5
Output: 5
Explanation:
Here are the non-negative integers <= 5 with their corresponding binary representations:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
Among them, only integer 3 disobeys the rule (two consecutive ones) and the other 5 satisfy the rule.


👨‍💻 Алгоритм:

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

2⃣Чтобы проверить, существует ли 1 на позиции x (считая от младшего значащего бита), в текущем числе n, поступаем следующим образом. Сдвигаем двоичную 1 x−1 раз влево, чтобы получить число y, которое имеет 1 только на x-й позиции. Логическое И числа n и y даст результат 1 только если n содержит 1 на позиции x.

3⃣В конце подсчитываем и возвращаем количество чисел в диапазоне [0, n], не содержащих последовательных единиц.

😎 Решение:
class Solution {
fun findIntegers(num: Int): Int {
var count = 0
for (i in 0..num) {
if (check(i)) {
count++
}
}
return count
}

fun check(n: Int): Boolean {
var i = 31
while (i > 0) {
if ((n and (1 shl i)) != 0 && (n and (1 shl (i - 1))) != 0) {
return false
}
i--
}
return true
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 284. Peeking Iterator
Сложность: medium

Создайте итератор, который поддерживает операцию peek (просмотр следующего элемента) на существующем итераторе, помимо операций hasNext (проверка наличия следующего элемента) и next (получение следующего элемента).

Реализуйте класс PeekingIterator:
PeekingIterator(Iterator<int> nums): Инициализирует объект с заданным итератором целых чисел.
int next(): Возвращает следующий элемент в массиве и перемещает указатель на следующий элемент.
boolean hasNext(): Возвращает true, если в массиве еще есть элементы.
int peek(): Возвращает следующий элемент в массиве без перемещения указателя.

Пример:
Input
["PeekingIterator", "next", "peek", "next", "next", "hasNext"]
[[[1, 2, 3]], [], [], [], [], []]
Output
[null, 1, 2, 2, 3, false]

Explanation
PeekingIterator peekingIterator = new PeekingIterator([1, 2, 3]); // [1,2,3]
peekingIterator.next(); // return 1, the pointer moves to the next element [1,2,3].
peekingIterator.peek(); // return 2, the pointer does not move [1,2,3].
peekingIterator.next(); // return 2, the pointer moves to the next element [1,2,3]
peekingIterator.next(); // return 3, the pointer moves to the next element [1,2,3]
peekingIterator.hasNext(); // return False


👨‍💻 Алгоритм:

1⃣Инициализация итератора:
В конструкторе класса PeekingIterator инициализируйте итератор и проверьте, есть ли следующий элемент. Если есть, установите его как next, иначе установите next в null.

2⃣Операция peek:
Метод peek возвращает значение next, не перемещая указатель итератора.

3⃣Операции next и hasNext:
Метод next возвращает текущее значение next, обновляет next к следующему элементу в итераторе и перемещает указатель итератора. Если нет следующего элемента, бросает исключение NoSuchElementException.
Метод hasNext возвращает true, если next не равно null, и false в противном случае.

😎 Решение:
class PeekingIterator(nums: Iterator<Int>) : Iterator<Int> {
private val iterator = nums
private var nextElement: Int? = if (iterator.hasNext()) iterator.next() else null

override fun next(): Int {
val currentElement = nextElement
nextElement = if (iterator.hasNext()) iterator.next() else null
return currentElement!!
}

override fun hasNext(): Boolean {
return nextElement != null
}

fun peek(): Int {
return nextElement!!
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 951. Flip Equivalent Binary Trees
Сложность: medium

Для бинарного дерева T мы можем определить операцию переворота следующим образом: выбираем любой узел и меняем местами левое и правое дочерние поддеревья. Бинарное дерево X эквивалентно бинарному дереву Y тогда и только тогда, когда мы можем сделать X равным Y после некоторого количества операций переворота. Учитывая корни двух бинарных деревьев root1 и root2, верните true, если эти два дерева эквивалентны перевороту, или false в противном случае.

Пример:
Input: root1 = [1,2,3,4,5,6,null,null,null,7,8], root2 = [1,3,2,null,6,4,5,null,null,null,null,8,7]
Output: true


👨‍💻 Алгоритм:

1⃣Если оба дерева пусты, они эквивалентны, вернуть true. Если одно дерево пустое, а другое нет, они не эквивалентны, вернуть false.

2⃣Если значения корней деревьев не совпадают, вернуть false.
Проверить два условия:
Левое поддерево первого дерева эквивалентно левому поддереву второго дерева и правое поддерево первого дерева эквивалентно правому поддереву второго дерева.
Левое поддерево первого дерева эквивалентно правому поддереву второго дерева и правое поддерево первого дерева эквивалентно левому поддереву второго дерева.

3⃣Вернуть true, если выполняется хотя бы одно из этих условий.

😎 Решение:
class TreeNode(var `val`: Int) {
var left: TreeNode? = null
var right: TreeNode? = null
}

class Solution {
fun flipEquiv(root1: TreeNode?, root2: TreeNode?): Boolean {
if (root1 == null && root2 == null) return true
if (root1 == null || root2 == null) return false
if (root1.`val` != root2.`val`) return false

return (flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right)) ||
(flipEquiv(root1.left, root2.right) && flipEquiv(root1.right, root2.left))
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 460. LFU Cache
Сложность: hard

Спроектируйте и реализуйте структуру данных для кеша с наименьшим количеством использования (Least Frequently Used, LFU).

Реализуйте класс LFUCache:

LFUCache(int capacity): Инициализирует объект с указанной вместимостью структуры данных.
int get(int key): Возвращает значение ключа, если ключ существует в кеше. В противном случае возвращает -1.
void put(int key, int value): Обновляет значение ключа, если он уже присутствует, или вставляет ключ, если его еще нет. Когда кеш достигает своей вместимости, он должен аннулировать и удалить ключ, используемый наименее часто, перед вставкой нового элемента. В этой задаче, если имеется несколько ключей с одинаковой частотой использования, аннулируется наименее недавно использованный ключ.
Чтобы определить наименее часто используемый ключ, для каждого ключа в кеше поддерживается счетчик использования. Ключ с наименьшим счетчиком использования является наименее часто используемым ключом.
Когда ключ впервые вставляется в кеш, его счетчик использования устанавливается на 1 (из-за операции put). Счетчик использования для ключа в кеше увеличивается при вызове операции get или put для этого ключа.

Функции get и put должны иметь среднюю временную сложность O(1).

Пример:
Input
["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]


👨‍💻 Алгоритм:

1⃣insert(int key, int frequency, int value):
Вставить пару частота-значение в cache с заданным ключом.
Получить LinkedHashSet, соответствующий данной частоте (по умолчанию пустой Set), и вставить в него ключ.

2⃣int get(int key):
Если ключа нет в кеше, вернуть -1.
Получить частоту и значение из кеша.
Удалить ключ из LinkedHashSet, связанного с частотой.
Если minf == frequency и LinkedHashSet пуст, увеличить minf на 1 и удалить запись частоты из frequencies.
Вызвать insert(key, frequency + 1, value).
Вернуть значение.

3⃣void put(int key, int value):
Если capacity <= 0, выйти.
Если ключ существует, обновить значение и вызвать get(key).
Если размер кеша равен capacity, удалить первый элемент из LinkedHashSet, связанного с minf, и из кеша.
Установить minf в 1.
Вызвать insert(key, 1, value).

😎 Решение:
class LFUCache(private val capacity: Int) {
private var minf = 0
private val cache = mutableMapOf<Int, Pair<Int, MutableIterator<Pair<Int, Int>>>>()
private val frequencies = mutableMapOf<Int, LinkedHashSet<Pair<Int, Int>>>()

private fun insert(key: Int, frequency: Int, value: Int) {
val freqSet = frequencies.getOrPut(frequency) { LinkedHashSet() }
freqSet.add(Pair(key, value))
cache[key] = Pair(frequency, freqSet.iterator())
}
fun get(key: Int): Int {
val entry = cache[key] ?: return -1
val (frequency, iter) = entry
val kv = iter.next()
frequencies[frequency]?.remove(kv)
if (frequencies[frequency]?.isEmpty() == true) {
frequencies.remove(frequency)
if (minf == frequency) minf++
}
insert(key, frequency + 1, kv.second)
return kv.second
}
fun put(key: Int, value: Int) {
if (capacity <= 0) return
if (cache.containsKey(key)) {
val (frequency, iter) = cache[key]!!
iter.next().second = value
get(key)
return
}
if (cache.size == capacity) {
val kv = frequencies[minf]?.iterator()?.next()
cache.remove(kv?.first)
frequencies[minf]?.remove(kv)
if (frequencies[minf]?.isEmpty() == true) frequencies.remove(minf)
}
minf = 1
insert(key, 1, value)
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1273. Delete Tree Nodes
Сложность: medium

Дерево, укорененное в узле 0, задано следующим образом: количество узлов - nodes; значение i-го узла - value[i]; родитель i-го узла - parent[i]. Удалите все поддеревья, сумма значений узлов которых равна нулю. Верните количество оставшихся узлов в дереве.

Пример:
Input: nodes = 7, parent = [-1,0,0,1,2,2,2], value = [1,-2,4,0,-2,-1,-1]
Output: 2


👨‍💻 Алгоритм:

1⃣Постройте дерево из заданных узлов, значений и родителей.

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

3⃣Удалите отмеченные узлы и их поддеревья и верните количество оставшихся узлов.

😎 Решение:
class Solution {
fun deleteTreeNodes(nodes: Int, parent: IntArray, value: IntArray): Int {
val tree = mutableMapOf<Int, MutableList<Int>>()
for (i in 0 until nodes) {
tree.computeIfAbsent(parent[i]) { mutableListOf() }.add(i)
}

fun dfs(node: Int): Pair<Int, Int> {
var totalSum = value[node]
var totalCount = 1
tree[node]?.forEach { child ->
val (childSum, childCount) = dfs(child)
totalSum += childSum
totalCount += childCount
}
return if (totalSum == 0) Pair(0, 0) else Pair(totalSum, totalCount)
}

return dfs(0).second
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1136. Parallel Courses
Сложность: medium

Вам дано целое число n, которое указывает, что есть n курсов, обозначенных от 1 до n. Вам также дан массив relations, где relations[i] = [prevCoursei, nextCoursei], представляющий собой зависимость между курсами: курс prevCoursei должен быть пройден до курса nextCoursei.

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

Пример:
Input: n = 3, relations = [[1,3],[2,3]]
Output: 2
Explanation: The figure above represents the given graph.
In the first semester, you can take courses 1 and 2.
In the second semester, you can take course 3.


👨‍💻 Алгоритм:

1⃣ Постройте направленный граф из зависимостей (relations).

2⃣Реализуйте функцию dfsCheckCycle для проверки наличия цикла в графе.

3⃣Реализуйте функцию dfsMaxPath для вычисления длины самого длинного пути в графе. Если цикл найден, верните -1. Иначе верните длину самого длинного пути.

😎 Решение:
class Solution {
fun minimumSemesters(N: Int, relations: Array<IntArray>): Int {
val graph = Array(N + 1) { mutableListOf<Int>() }
for (relation in relations) {
graph[relation[0]].add(relation[1])
}

val visited = IntArray(N + 1)
for (node in 1..N) {
if (dfsCheckCycle(node, graph, visited) == -1) {
return -1
}
}

val visitedLength = IntArray(N + 1)
var maxLength = 1
for (node in 1..N) {
val length = dfsMaxPath(node, graph, visitedLength)
maxLength = maxOf(length, maxLength)
}
return maxLength
}

private fun dfsCheckCycle(node: Int, graph: Array<MutableList<Int>>, visited: IntArray): Int {
if (visited[node] != 0) {
return visited[node]
} else {
visited[node] = -1
}
for (endNode in graph[node]) {
if (dfsCheckCycle(endNode, graph, visited) == -1) {
return -1
}
}
visited[node] = 1
return 1
}

private fun dfsMaxPath(node: Int, graph: Array<MutableList<Int>>, visitedLength: IntArray): Int {
if (visitedLength[node] != 0) {
return visitedLength[node]
}
var maxLength = 1
for (endNode in graph[node]) {
val length = dfsMaxPath(endNode, graph, visitedLength)
maxLength = maxOf(length + 1, maxLength)
}
visitedLength[node] = maxLength
return maxLength
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 276. Paint Fence
Сложность: medium

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

Пример:
Input: n = 3, k = 2
Output: 6
Explanation: All the possibilities are shown.
Note that painting all the posts red or all the posts green is invalid because there cannot be three posts in a row with the same color.


👨‍💻 Алгоритм:

1⃣Инициализация и определение вспомогательной функции:
Определить хеш-таблицу memo, где memo[i] представляет количество способов покрасить i столбцов.
Определить функцию totalWays, которая будет определять количество способов покрасить i столбцов.

2⃣Реализация базы и проверка кэша:
В функции totalWays проверить базовые случаи: вернуть k, если i == 1, и вернуть k * k, если i == 2.
Проверить, рассчитан ли аргумент i и сохранен ли в memo. Если да, вернуть memo[i].

3⃣Расчет с использованием рекуррентного соотношения:
В противном случае использовать рекуррентное соотношение для вычисления memo[i], сохранить результат в memo[i] и вернуть его.
Вызвать и вернуть totalWays(n).

😎 Решение:
class Solution {
fun numWays(n: Int, k: Int): Int {
if (n == 1) return k

var twoPostsBack = k
var onePostBack = k * k

for (i in 3..n) {
val curr = (k - 1) * (onePostBack + twoPostsBack)
twoPostsBack = onePostBack
onePostBack = curr
}

return onePostBack
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 485. Max Consecutive Ones
Сложность: Easy

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

Пример:
Input: nums = [1,1,0,1,1,1]
Output: 3
Explanation: The first two digits or the last three digits are consecutive 1s. The maximum number of consecutive 1s is 3.


👨‍💻 Алгоритм:

1⃣Поддерживайте счетчик для подсчета единиц и увеличивайте его на 1 при встрече единицы.

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

3⃣В конце верните максимальное значение.

😎 Решение:
class Solution {
fun findMaxConsecutiveOnes(nums: IntArray): Int {
var count = 0
var maxCount = 0
for (num in nums) {
if (num == 1) {
count += 1
} else {
maxCount = maxOf(maxCount, count)
count = 0
}
}
return maxOf(maxCount, count)
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 380. Insert Delete GetRandom O(1)
Сложность: medium

Реализуйте класс RandomizedSet:

RandomizedSet(): Инициализирует объект RandomizedSet.
bool insert(int val): Вставляет элемент val в множество, если его там нет. Возвращает true, если элемент отсутствовал, и false в противном случае.
bool remove(int val): Удаляет элемент val из множества, если он присутствует. Возвращает true, если элемент присутствовал, и false в противном случае.
int getRandom(): Возвращает случайный элемент из текущего множества элементов (гарантируется, что по крайней мере один элемент существует при вызове этого метода). Каждый элемент должен иметь равную вероятность быть возвращенным.
Вы должны реализовать функции класса таким образом, чтобы каждая функция работала в среднем за O(1) по времени.

Пример:
Input
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
Output
[null, true, false, true, 2, true, false, 2]


👨‍💻 Алгоритм:

1⃣Создать словарь для хранения значений и их индексов, а также список для хранения значений.

2⃣Метод insert(val): Проверить наличие значения в словаре. Если отсутствует, добавить значение в список и обновить словарь с новым индексом.
Метод remove(val): Проверить наличие значения в словаре. Если присутствует, заменить удаляемое значение последним элементом списка, обновить его индекс в словаре, удалить последний элемент из списка и удалить значение из словаря.

3⃣Метод getRandom(): Возвращать случайный элемент из списка, используя встроенную функцию генерации случайных чисел.

😎 Решение:
import kotlin.random.Random

class RandomizedSet {
private val dict = mutableMapOf<Int, Int>()
private val list = mutableListOf<Int>()

fun insert(val: Int): Boolean {
if (dict.containsKey(val)) {
return false
}
dict[val] = list.size
list.add(val)
return true
}

fun remove(val: Int): Boolean {
val index = dict[val] ?: return false
val lastElement = list.removeAt(list.size - 1)
if (index < list.size) {
list[index] = lastElement
dict[lastElement] = index
}
dict.remove(val)
return true
}

fun getRandom(): Int {
return list[Random.nextInt(list.size)]
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1020. Number of Enclaves
Сложность: medium

Вам дана двоичная матричная сетка m x n, где 0 обозначает морскую ячейку, а 1 - сухопутную. Ход состоит из перехода от одной сухопутной ячейки к другой соседней (в 4-х направлениях) или выхода за границу сетки. Верните количество сухопутных ячеек в сетке, для которых мы не можем выйти за границу сетки за любое количество ходов.

Пример:
Input: grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
Output: 3


👨‍💻 Алгоритм:

1⃣Обработка граничных сухопутных ячеек:
Пройдитесь по всем ячейкам, которые находятся на границе сетки (первый и последний ряды, первый и последний столбцы). Если ячейка содержит 1, начните поиск в глубину (DFS) или поиск в ширину (BFS), чтобы пометить все достижимые из нее сухопутные ячейки как посещенные.

2⃣Проверка всех ячеек:
Пройдите по всем ячейкам матрицы, считая количество сухопутных ячеек, которые не были посещены в предыдущем шаге.

3⃣Возврат результата:
Верните количество не посещенных сухопутных ячеек.

😎 Решение:
class Solution {
fun numEnclaves(grid: Array<IntArray>): Int {
val m = grid.size
val n = grid[0].size

fun dfs(x: Int, y: Int) {
if (x < 0 || y < 0 || x >= m || y >= n || grid[x][y] != 1) {
return
}
grid[x][y] = 0
dfs(x + 1, y)
dfs(x - 1, y)
dfs(x, y + 1)
dfs(x, y - 1)
}

for (i in 0 until m) {
if (grid[i][0] == 1) {
dfs(i, 0)
}
if (grid[i][n - 1] == 1) {
dfs(i, n - 1)
}
}

for (j in 0 until n) {
if (grid[0][j] == 1) {
dfs(0, j)
}
if (grid[m - 1][j] == 1) {
dfs(m - 1, j)
}
}

var count = 0
for (i in 0 until m) {
for (j in 0 until n) {
if (grid[i][j] == 1) {
count += 1
}
}
}

return count
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1219. Path with Maximum Gold
Сложность: medium

В золотом руднике размером m x n каждая ячейка содержит целое число, представляющее количество золота в этой ячейке, или 0, если она пуста.

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

Пример:
Input: grid = [[0,6,0],[5,8,7],[0,9,0]]
Output: 24
Explanation:
[[0,6,0],
[5,8,7],
[0,9,0]]
Path to get the maximum gold, 9 -> 8 -> 7.


👨‍💻 Алгоритм:

1⃣Инициализация и подготовка:
Инициализируйте константный массив DIRECTIONS для направления перемещений.
Определите количество строк и столбцов в сетке.
Инициализируйте переменную maxGold для хранения максимального количества собранного золота.

2⃣Функция DFS и обратный трек:
Реализуйте функцию dfsBacktrack для поиска пути с максимальным золотом с помощью DFS и обратного трека.
Обрабатывайте базовый случай, проверяя выход за пределы сетки или ячейки без золота.
Пометьте текущую ячейку как посещённую и сохраните её значение.
Исследуйте каждую из четырёх смежных ячеек и обновите максимальное количество золота, если найден лучший путь.
Сбросьте текущую ячейку до её исходного значения для дальнейших исследований.

3⃣Поиск максимального золота:
Используйте вложенные циклы для каждой ячейки в сетке, чтобы найти максимальное количество золота, начиная с этой ячейки, с помощью функции dfsBacktrack.
Обновите maxGold при нахождении лучшего пути.
Верните maxGold.

😎 Решение:
class Solution {
private val directions = intArrayOf(0, 1, 0, -1, 0)

fun getMaximumGold(grid: Array<IntArray>): Int {
val rows = grid.size
val cols = grid[0].size
var maxGold = 0

for (row in 0 until rows) {
for (col in 0 until cols) {
maxGold = maxOf(maxGold, dfsBacktrack(grid, rows, cols, row, col))
}
}
return maxGold
}

private fun dfsBacktrack(grid: Array<IntArray>, rows: Int, cols: Int, row: Int, col: Int): Int {
if (row < 0 || col < 0 || row >= rows || col >= cols || grid[row][col] == 0) {
return 0
}
val originalVal = grid[row][col]
grid[row][col] = 0
var maxGold = 0

for (i in 0 until 4) {
maxGold = maxOf(maxGold, dfsBacktrack(grid, rows, cols, row + directions[i], col + directions[i + 1]))
}

grid[row][col] = originalVal
return maxGold + originalVal
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 99. Recover Binary Search Tree
Сложность: medium

Вам дан корень бинарного дерева поиска (BST), в котором значения ровно двух узлов дерева были поменяны местами по ошибке. Восстановите дерево, не изменяя его структуру.

Пример:
Input: root = [1,3,null,null,2]
Output: [3,1,null,null,2]
Explanation: 3 cannot be a left child of 1 because 3 > 1. Swapping 1 and 3 makes the BST valid.


👨‍💻 Алгоритм:

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

2⃣Определите два поменянных местами элемента x и y в почти отсортированном массиве за линейное время.

3⃣Повторно пройдите по дереву. Измените значение x на y и значение y на x.

😎 Решение:
class TreeNode(var value: Int, var left: TreeNode? = null, var right: TreeNode? = null)

class Solution {
fun recoverTree(root: TreeNode?) {
fun inorder(r: TreeNode?): List<Int> {
if (r == null) return emptyList()
return inorder(r.left) + r.value + inorder(r.right)
}

fun findTwoSwapped(nums: List<Int>): Pair<Int, Int> {
val n = nums.size
var x: Int? = null
var y: Int? = null

for (i in 0 until n - 1) {
if (nums[i + 1] < nums[i]) {
y = nums[i + 1]
if (x == null) {
x = nums[i]
} else {
break
}
}
}
return Pair(x!!, y!!)
}

fun recover(r: TreeNode?, x: Int, y: Int, count: IntArray) {
if (r == null || count[0] == 0) return

if (r.value == x || r.value == y) {
r.value = if (r.value == x) y else x
count[0]--
if (count[0] == 0) return
}
recover(r.left, x, y, count)
recover(r.right, x, y, count)
}

val nums = inorder(root)
val (x, y) = findTwoSwapped(nums)
recover(root, x, y, intArrayOf(2))
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1470. Shuffle the Array
Сложность: easy

Дан массив nums, состоящий из 2n элементов в форме [x1, x2, ..., xn, y1, y2, ..., yn].

Верните массив в форме [x1, y1, x2, y2, ..., xn, yn].

Пример:
Input: nums = [2,5,1,3,4,7], n = 3
Output: [2,3,5,4,1,7]
Explanation: Since x1=2, x2=5, x3=1, y1=3, y2=4, y3=7 then the answer is [2,3,5,4,1,7].


👨‍💻 Алгоритм:

1⃣Создайте массив result размером 2 * n.

2⃣Итеративно пройдите по массиву nums от 0 до n - 1:
Сохраните элемент xi+1, то есть nums[i], в индекс 2 * i массива result.
Сохраните элемент yi+1, то есть nums[i + n], в индекс 2 * i + 1 массива result.

3⃣Верните массив result.

😎 Решение:
class Solution {
fun shuffle(nums: IntArray, n: Int): IntArray {
val result = IntArray(2 * n)
for (i in 0 until n) {
result[2 * i] = nums[i]
result[2 * i + 1] = nums[n + i]
}
return result
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 190. Reverse Bits
Сложность: easy

Переверните биты заданного 32-битного беззнакового целого числа.

Пример:
Input: n = 00000010100101000001111010011100
Output: 964176192 (00111001011110000010100101000000)
Explanation: The input binary string 00000010100101000001111010011100 represents the unsigned integer 43261596, so return 964176192 which its binary representation is 00111001011110000010100101000000.
Example 2:


👨‍💻 Алгоритм:

1⃣Итерируем по байтам целого числа, используя побитовую операцию И (n & 0xff) с маской 11111111, чтобы извлечь крайний правый байт числа.

2⃣Для каждого байта сначала переворачиваем биты внутри байта с помощью функции reverseByte(byte). Затем сдвигаем перевернутые биты на их окончательные позиции.

3⃣В функции reverseByte(byte) используем технику мемоизации, которая сохраняет результат функции и возвращает его непосредственно при последующих вызовах с тем же входным значением. Мемоизация — это компромисс между использованием памяти и объемом вычислений.

😎 Решение:
class Solution {
private val cache = mutableMapOf<UInt, UInt>()

fun reverseByte(byte: UInt): UInt {
return cache.getOrElse(byte) {
val value = ((byte * 0x0202020202u) and 0x010884422010u) % 1023u
cache[byte] = value
value
}
}

fun reverseBits(n: UInt): UInt {
var ret = 0u
var power = 24
var n = n
while (n != 0u) {
ret += reverseByte(n and 0xffu) shl power
n = n shr 8
power -= 8
}
return ret
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊1