Swift | LeetCode
1.49K subscribers
126 photos
1.06K links
Cайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp

Тесты t.iss.one/+bn3i_aLL0-A2ZGMy
Вопросы собесов t.iss.one/+wtkjBoN6OI5hNGEy
Вакансии t.iss.one/+3o9-Ytdiv_E5OGIy
Download Telegram
Задача: 1673. Find the Most Competitive Subsequence
Сложность: medium

Дан целочисленный массив nums и положительное целое число k. Верните наиболее конкурентоспособную подпоследовательность массива nums размера k.

Подпоследовательность массива — это результирующая последовательность, полученная путем удаления некоторых (возможно, нуля) элементов из массива.

Мы определяем, что подпоследовательность a более конкурентоспособна, чем подпоследовательность b (одинаковой длины), если в первой позиции, где они различаются, подпоследовательность a имеет число меньше, чем соответствующее число в b. Например, [1,3,4] более конкурентоспособна, чем [1,3,5], потому что первая позиция, где они различаются, это последнее число, и 4 меньше, чем 5.

Пример:
Input: nums = [3,5,2,6], k = 2
Output: [2,6]
Explanation: Among the set of every possible subsequence: {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]}, [2,6] is the most competitive.


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

1⃣Создайте двустороннюю очередь (deque), которая будет хранить выбранную подпоследовательность.

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

3⃣В конце получите первые k элементов из очереди и создайте результирующий массив.

😎 Решение:
class Solution {
func mostCompetitive(_ nums: [Int], _ k: Int) -> [Int] {
var queue = [Int]()
var additionalCount = nums.count - k

for num in nums {
while !queue.isEmpty && queue.last! > num && additionalCount > 0 {
queue.removeLast()
additionalCount -= 1
}
queue.append(num)
}

return Array(queue.prefix(k))
}
}


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

Вам дан список эквивалентных пар строк synonyms, где synonyms[i] = [si, ti] означает, что si и ti являются эквивалентными строками. Вам также дан текст предложения. Верните все возможные синонимичные предложения, отсортированные лексикографически.

Пример:
Input: synonyms = [["happy","joy"],["sad","sorrow"],["joy","cheerful"]], text = "I am happy today but was sad yesterday"
Output: ["I am cheerful today but was sad yesterday","I am cheerful today but was sorrow yesterday","I am happy today but was sad yesterday","I am happy today but was sorrow yesterday","I am joy today but was sad yesterday","I am joy today but was sorrow yesterday"]


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

1⃣Построить граф синонимов, используя структуру данных, такую как Union-Find или просто с использованием DFS/BFS.

2⃣Пройти по каждому слову в предложении и найти все возможные синонимы.
Сгенерировать все возможные комбинации предложений.

3⃣Отсортировать полученные предложения лексикографически.

😎 Решение:
class Solution {
func generateSentences(_ synonyms: [[String]], _ text: String) -> [String] {
var graph = [String: Set<String>]()

for pair in synonyms {
graph[pair[0], default: Set()].insert(pair[1])
graph[pair[1], default: Set()].insert(pair[0])
}

let words = text.split(separator: " ").map { String($0) }
var synonymGroups = [[String]]()
for word in words {
synonymGroups.append(findSynonyms(graph, word))
}

var sentences = [String]()
var sentence = ""
generate(&sentences, synonymGroups, &sentence, 0)
return sentences.sorted()
}

private func findSynonyms(_ graph: [String: Set<String>], _ word: String) -> [String] {
var synonyms = Set<String>()
var stack = [word]
while !stack.isEmpty {
let w = stack.removeLast()
if synonyms.insert(w).inserted {
for neighbor in graph[w] ?? [] {
stack.append(neighbor)
}
}
}
return Array(synonyms).sorted()
}

private func generate(_ sentences: inout [String], _ groups: [[String]], _ sentence: inout String, _ index: Int) {
if index == groups.count {
sentences.append(sentence.trimmingCharacters(in: .whitespaces))
return
}
for word in groups[index] {
let original = sentence
sentence += " " + word
generate(&sentences, groups, &sentence, index + 1)
sentence = original
}
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1
Задача: 665. Non-decreasing Array
Сложность: medium

Дан массив nums из n целых чисел. Ваша задача - проверить, можно ли сделать его неубывающим, изменив не более одного элемента.

Мы определяем массив как неубывающий, если для каждого i (индексация с 0), такого что 0 <= i <= n - 2, выполняется условие nums[i] <= nums[i + 1].

Пример:
Input: nums = [4,2,3]
Output: true
Explanation: You could modify the first 4 to 1 to get a non-decreasing array.


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

1⃣Инициализация переменных:
Завести переменную count для подсчета числа изменений.
Проверить последовательность чисел в массиве nums.

2⃣Проверка условий:
Если nums[i] > nums[i + 1], то увеличиваем count.
Если count превышает 1, возвращаем false, так как больше одного изменения недопустимо.
Если nums[i - 1] > nums[i + 1] и nums[i] > nums[i + 2], то возвращаем false.

3⃣Возврат результата:
Если количество изменений не превышает 1, вернуть true.

😎 Решение:
class Solution {
func checkPossibility(_ nums: [Int]) -> Bool {
var nums = nums
var count = 0

for i in 1..<nums.count {
if nums[i] < nums[i - 1] {
if count > 0 {
return false
}
count += 1
if i == 1 || nums[i] >= nums[i - 2] {
nums[i - 1] = nums[i]
} else {
nums[i] = nums[i - 1]
}
}
}

return true
}
}


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

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

Пример:
Input: root1 = [2,1,4], root2 = [1,0,3]
Output: [0,1,1,2,3,4]


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

1⃣Выполните итеративный обход в порядке возрастания обоих деревьев параллельно.

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

3⃣Верните выходной список.

😎 Решение:
class TreeNode {
var val: Int
var left: TreeNode?
var right: TreeNode?
init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
}

class Solution {
func getAllElements(_ root1: TreeNode?, _ root2: TreeNode?) -> [Int] {
var stack1 = [TreeNode](), stack2 = [TreeNode]()
var output = [Int]()
var root1 = root1
var root2 = root2

while root1 != nil || root2 != nil || !stack1.isEmpty || !stack2.isEmpty {
while root1 != nil {
stack1.append(root1!)
root1 = root1!.left
}
while root2 != nil {
stack2.append(root2!)
root2 = root2!.left
}
if stack2.isEmpty || (!stack1.isEmpty && stack1.last!.val <= stack2.last!.val) {
root1 = stack1.removeLast()
output.append(root1!.val)
root1 = root1!.right
} else {
root2 = stack2.removeLast()
output.append(root2!.val)
root2 = root2!.right
}
}
return output
}
}


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

При наличии массива ключевых слов и строки a выделите все ключевые слова [i] жирным шрифтом. Все буквы между тегами <b> и </b> выделяются жирным шрифтом.

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

Пример:
Input: words = ["ab","bc"], s = "aabcd"
Output: "a<b>abc</b>d"


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

1⃣Создайте массив для хранения флагов, указывающих, какие символы в строке a должны быть выделены жирным шрифтом.

2⃣Пройдите по каждому ключевому слову и отметьте соответствующие позиции в массиве флагов.

3⃣Постройте результирующую строку, добавляя теги <b> и </b> на основе массива флагов.

😎 Решение:
func addBoldTags(_ keywords: [String], _ s: String) -> String {
var bold = Array(repeating: false, count: s.count)
let sArray = Array(s)

for word in keywords {
var start = s.range(of: word)
while start != nil {
let range = start!.lowerBound..<s.index(start!.lowerBound, offsetBy: word.count)
for i in range {
bold[s.distance(from: s.startIndex, to: i)] = true
}
start = s.range(of: word, range: range.upperBound..<s.endIndex)
}
}

var result = ""
var i = 0
while i < s.count {
if bold[i] {
result += "<b>"
while i < s.count && bold[i] {
result.append(sArray[i])
i += 1
}
result += "</b>"
} else {
result.append(sArray[i])
i += 1
}
}

return result
}


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

Вам дана серия видеоклипов со спортивного соревнования, длительность которых составляет несколько секунд. Эти видеоклипы могут накладываться друг на друга и иметь различную длину. Каждый видеоклип описывается массивом clips, где clips[i] = [starti, endi] указывает, что i-й клип начинается в starti и заканчивается в endi. Мы можем произвольно разрезать эти клипы на сегменты. Например, клип [0, 7] может быть разрезан на сегменты [0, 1] + [1, 3] + [3, 7]. Верните минимальное количество клипов, необходимое для того, чтобы мы могли разрезать клипы на сегменты, охватывающие все спортивное событие [0, время]. Если задача невыполнима, верните -1.

Пример:
Input: clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], time = 10
Output: 3


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

1⃣Сортировка клипов:
Отсортируйте клипы по начальным значениям. Если начальные значения равны, отсортируйте по конечным значениям в убывающем порядке.

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

3⃣Проверка покрытия:
Продолжайте процесс, пока не покроете весь диапазон от 0 до T. Если в конце процесса достигнута или превышена точка T, верните количество использованных клипов, иначе верните -1.

😎 Решение:
class Solution {
func videoStitching(_ clips: [[Int]], _ T: Int) -> Int {
let clips = clips.sorted { $0[0] < $1[0] || ($0[0] == $1[0] && $0[1] > $1[1]) }
var end = -1, end2 = 0, res = 0

for clip in clips {
if end2 >= T || clip[0] > end2 {
break
} else if end < clip[0] && clip[0] <= end2 {
res += 1
end = end2
}
end2 = max(end2, clip[1])
}
return end2 >= T ? res : -1
}
}


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

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

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

Пример:
Input: arr = [1,5,7,8,5,3,4,2,1], difference = -2
Output: 4
Explanation: The longest arithmetic subsequence is [7,5,3,1].


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

1⃣Инициализируйте пустой хеш-таблицу dp и установите answer = 1.

2⃣Итеративно обработайте массив arr. Для каждого элемента arr[i]:
Вычислите before_a, максимальную длину арифметической подпоследовательности, заканчивающейся на arr[i] - difference:
- если arr[i] - difference существует в dp, установите before_a = dp[arr[i] - difference].
- в противном случае, установите before_a = 0.
Установите dp[arr[i]] = before_a + 1, обновите answer как answer = max(answer, dp[arr[i]]).

3⃣Верните answer после завершения итерации.

😎 Решение:
class Solution {
func longestSubsequence(_ arr: [Int], _ difference: Int) -> Int {
var dp = [Int: Int]()
var answer = 1

for a in arr {
let beforeA = dp[a - difference, default: 0]
dp[a] = beforeA + 1
answer = max(answer, dp[a]!)
}

return answer
}
}


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

Даны два целочисленных массива nums1 и nums2 одинаковой длины. Преимущество nums1 относительно nums2 — это количество индексов i, для которых nums1[i] > nums2[i].

Верните любую перестановку nums1, которая максимизирует его преимущество относительно nums2.

Пример:
Input: nums1 = [2,7,11,15], nums2 = [1,10,4,11]
Output: [2,11,7,15]


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

1⃣Отсортируйте nums1 и nums2. Для каждой карты a из отсортированного nums1 определите, может ли она побить текущую наименьшую карту b из отсортированного nums2. Если да, добавьте a в assigned[b], если нет, добавьте a в remaining.

2⃣После распределения всех карт из nums1, используйте assigned и remaining для построения итогового результата. Для каждой карты b из nums2, если assigned[b] не пуст, добавьте в результат последнюю карту из assigned[b], иначе добавьте последнюю карту из remaining.

3⃣Верните итоговый результат.

😎 Решение:
class Solution {
func advantageCount(_ A: [Int], _ B: [Int]) -> [Int] {
let sortedA = A.sorted()
let sortedB = B.enumerated().sorted { $0.element < $1.element }
var assigned = [Int: [Int]]()
var remaining = [Int]()

var j = 0
for a in sortedA {
if a > sortedB[j].element {
assigned[sortedB[j].element, default: []].append(a)
j += 1
} else {
remaining.append(a)
}
}

var result = [Int]()
for b in B {
if let val = assigned[b]?.popLast() {
result.append(val)
} else {
result.append(remaining.popLast()!)
}
}
return result
}
}


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

Вам дана сеть из n узлов, представленная в виде графа с матрицей смежности n x n, где i-й узел непосредственно связан с j-м узлом, если graph[i][j] == 1. Некоторые узлы изначально заражены вредоносным ПО. Если два узла соединены напрямую и хотя бы один из них заражен вредоносным ПО, то оба узла будут заражены вредоносным ПО. Такое распространение вредоносного ПО будет продолжаться до тех пор, пока не останется ни одного узла, который можно было бы заразить таким образом. Предположим, что M(initial) - это конечное число узлов, зараженных вредоносным ПО, во всей сети после прекращения распространения вредоносного ПО. Мы удалим из initial ровно один узел. Верните тот узел, удаление которого минимизирует M(initial). Если можно удалить несколько узлов, чтобы минимизировать M(initial), верните такой узел с наименьшим индексом. Обратите внимание, что если узел был удален из начального списка зараженных узлов, он все равно может быть заражен позже из-за распространения вредоносного ПО.

Пример:
Input: arr = [1,1,2,2,3,3,4,4,5,5], target = 8
Output: 20


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

1⃣Определить количество зараженных узлов после распространения вредоносного ПО для исходного списка initial.

2⃣Для каждого узла в initial удалить его и вычислить количество зараженных узлов после распространения вредоносного ПО.

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

😎 Решение:
class Solution {
func minMalwareSpread(_ graph: [[Int]], _ initial: [Int]) -> Int {
func dfs(_ node: Int, _ infected: inout Set<Int>) {
for neighbor in 0..<graph.count {
if graph[node][neighbor] == 1 && !infected.contains(neighbor) {
infected.insert(neighbor)
dfs(neighbor, &infected)
}
}
}

let n = graph.count
var initialSet = Set(initial)
let sortedInitial = initial.sorted()
var minInfected = Int.max
var bestNode = sortedInitial[0]

for node in sortedInitial {
var infected = initialSet
infected.remove(node)
for i in initialSet {
if i != node {
dfs(i, &infected)
}
}
if infected.count < minInfected {
minInfected = infected.count
bestNode = node
}
}

return bestNode
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from easyoffer
Я боялся, что провалю собеседование. Так появился easyoffer

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

Типа… ты потратил месяцы на то, чтобы учиться, писал pet-проекты, собирал резюме, рассылаешь отклики — и всё может закончиться на одном-единственном вопросе, на который ты не знаешь ответ.

Я реально боялся.
Я смотрел видео mock-собеседований на YouTube, останавливал каждое, выписывал вопросы в Notion. Потом вручную писал к ним ответы. И потом ещё по нескольку раз перечитывал. Такой вот "тренажёр" на коленке.

📎 (там на картинке — один из моих реальных списков в Notion, ставь 🔥 если тоже так делал)

В какой-то момент я посчитал — у меня уже было выписано больше 500 вопросов. Я почувствовал ужас.
Потому что невозможно всё это зазубрить. А что, если спросят как раз тот, к которому я не успел подготовиться?..

Тогда и пришла идея

А что если понять, какие из вопросов встречаются чаще всего? Чтобы не учить всё подряд, а сфокусироваться на главном.

Так родился easyoffer.

Сначала — просто как пет-проект, чтобы показать в резюме и подготовиться к собесам. А потом оказалось, что он реально помогает людям. За первые месяцы его посетили сотни тысяч человек. И я понял: это больше, чем просто пет-проект.

Сейчас я делаю EasyOffer 2.0
И уже не один, а вместе с вами.

В новой версии будут:
– вопросы из реальных собесов, с фильтрацией по грейду, компании, типу интервью
– тренажёр с карточками (по принципу интервальных повторений — как в Anki)
– база задач с интервью
– тренажёр «реальное собеседование», чтобы отрепетировать как в жизни

Каждая фича упрощает и сокращает время на подготовку. Все эти штуки я бы мечтал иметь, когда сам готовился к собеседованиям.

Я делаю всё на свои деньги. Никаких инвесторов. Только вы и я.

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

Все, кто поддержат проект до релиза, получат:

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

Поддержать 👉 https://planeta.ru/campaigns/easyoffer

Спасибо, что верите в этот проект 🙌
Задача: 118. Pascal's Triangle
Сложность:easy

Дано целое число numRows. Верните первые numRows строк треугольника Паскаля.

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

Пример:
Input: numRows = 5
Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]


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

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

2⃣Сначала мы создаём общий список треугольника, который будет хранить каждую строку как подсписок. Затем мы проверяем особый случай, когда число строк равно 0, так как в противном случае мы бы вернули [1].

3⃣Поскольку numRows всегда больше 0, мы можем инициализировать треугольник первой строкой [1] и продолжить заполнять строки следующим образом:

😎 Решение:
class Solution {
func generate(_ numRows: Int) -> [[Int]] {
var triangle = [[Int]]()

for rowNum in 0..<numRows {
var row = Array(repeating: 0, count: rowNum + 1)
row[0] = 1
row[row.count - 1] = 1

for j in 1..<row.count - 1 {
row[j] = triangle[rowNum - 1][j - 1] + triangle[rowNum - 1][j]
}

triangle.append(row)
}

return triangle
}
}


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

Вам дан целочисленный массив arr. За один ход вы можете выбрать палиндромный подмассив arr[i], arr[i + 1], ..., arr[j], где i <= j, и удалить этот подмассив из данного массива. Обратите внимание, что после удаления подмассива элементы слева и справа от него перемещаются, чтобы заполнить пробел, образовавшийся в результате удаления. Верните минимальное количество ходов, необходимое для удаления всех чисел из массива.

Пример:
Input: arr = [1,2]
Output: 2


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

1⃣Базовый случай:
Если подмассив состоит из одного элемента, то его удаление займет 1 ход, поэтому dp[i][i] = 1.

2⃣Рекурсивный случай:
Если arr[i] == arr[j], то мы можем удалить их в одном ходе, если подмассив arr[i+1...j-1] можно удалить за dp[i+1][j-1] ходов, тогда dp[i][j] = dp[i+1][j-1] (если удалим подмассив arr[i+1...j-1] и затем удалим arr[i] и arr[j]).

3⃣В противном случае, минимальное количество ходов для удаления подмассива arr[i...j] будет равно 1 + минимум ходов для удаления каждого из подмассивов arr[i...k] и arr[k+1...j], где i <= k < j. То есть, dp[i][j] = min(dp[i][k] + dp[k+1][j]) для всех k от i до j-1.

😎 Решение:
func minMovesToDelete(_ arr: [Int]) -> Int {
let n = arr.count
var dp = Array(repeating: Array(repeating: 0, count: n), count: n)

for i in 0..<n {
dp[i][i] = 1
}

for length in 2...n {
for i in 0...(n - length) {
let j = i + length - 1
if arr[i] == arr[j] {
dp[i][j] = length > 2 ? dp[i + 1][j - 1] : 1
} else {
dp[i][j] = Int.max
for k in i..<j {
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j])
}
}
}
}

return dp[0][n - 1]
}


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

Сообщение, содержащее буквы от A до Z, можно закодировать в числа с использованием следующего соответствия:

- 'A' -> "1"
- 'B' -> "2"
- ...
- 'Z' -> "26"

Для декодирования закодированного сообщения все цифры должны быть сгруппированы и затем отображены обратно в буквы с использованием обратного соответствия (существует несколько способов). Например, "11106" можно представить как:

- "AAJF" с группировкой (1 1 10 6)
- "KJF" с группировкой (11 10 6)

Обратите внимание, что группировка (1 11 06) недопустима, потому что "06" не может быть преобразовано в 'F', так как "6" отличается от "06".

Для данной строки s, содержащей только цифры, верните количество способов декодирования.

Тестовые случаи сформированы таким образом, что ответ укладывается в 32-битное целое число.

Пример:
Input: s = "12"
Output: 2
Explanation: "12" could be decoded as "AB" (1 2) or "L" (12).


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


1⃣Входим в рекурсию с данной строкой, начиная с индекса 0.

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

3⃣Мемоизация помогает снизить сложность, которая иначе была бы экспоненциальной. Мы проверяем словарь memo, чтобы увидеть, существует ли уже результат для данной подстроки. Если результат уже находится в memo, мы возвращаем этот результат. В противном случае количество способов для данной строки определяется путем рекурсивного вызова функции с индексом +1 для следующей подстроки и индексом +2 после проверки на валидность двузначного декодирования. Результат также сохраняется в memo с ключом как текущий индекс, чтобы сохранить его для будущих пересекающихся подзадач.


😎 Решение:
import Foundation

class Solution {
private var memo: [Int: Int] = [:]

private func recursiveWithMemo(_ index: Int, _ s: String) -> Int {
if index == s.count {
return 1
}

let startIndex = s.index(s.startIndex, offsetBy: index)
let firstChar = s[startIndex]

if firstChar == "0" {
return 0
}

if index == s.count - 1 {
return 1
}

if let cachedResult = memo[index] {
return cachedResult
}

let nextIndex = s.index(after: startIndex)
var answer = recursiveWithMemo(index + 1, s)

if index < s.count - 1 {
let secondIndex = s.index(after: nextIndex)
let range = startIndex..<secondIndex
let number = Int(s[range])!

if number <= 26 {
answer += recursiveWithMemo(index + 2, s)
}
}

memo[index] = answer
return answer
}

func numDecodings(_ s: String) -> Int {
return recursiveWithMemo(0, s)
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1337. The K Weakest Rows in a Matrix
Сложность: easy

Вам дана бинарная матрица размером m x n mat, состоящая из 1 (представляющих солдат) и 0 (представляющих гражданских лиц). Солдаты расположены перед гражданскими лицами. То есть, все 1 будут расположены слева от всех 0 в каждой строке.

Строка i слабее строки j, если выполнено одно из следующих условий:

Количество солдат в строке i меньше, чем в строке j.
Обе строки имеют одинаковое количество солдат, но i < j.
Верните индексы k самых слабых строк в матрице, упорядоченных от самой слабой к самой сильной.

Пример:
Input: mat = 
[[1,1,0,0,0],
[1,1,1,1,0],
[1,0,0,0,0],
[1,1,0,0,0],
[1,1,1,1,1]],
k = 3
Output: [2,0,3]
Explanation:
The number of soldiers in each row is:
- Row 0: 2
- Row 1: 4
- Row 2: 1
- Row 3: 2
- Row 4: 5
The rows ordered from weakest to strongest are [2,0,3,1,4].


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

1⃣Вычислите силу каждой строки матрицы и поместите пары сила/индекс в массив. Для каждой строки подсчитайте количество единиц (солдат) до первой нуля (гражданского), чтобы определить силу строки.

2⃣Отсортируйте массив пар по возрастанию силы, а в случае равенства силы — по возрастанию индекса. Для этого потребуется реализовать компаратор, который сравнивает сначала силу, а затем индекс.

3⃣Извлеките первые k индексов из отсортированного массива и верните их. Эти индексы будут соответствовать k самым слабым строкам в порядке от самой слабой к самой сильной.

😎 Решение
class Solution {
func kWeakestRows(_ mat: [[Int]], _ k: Int) -> [Int] {
let m = mat.count
let n = mat[0].count

var pairs = [(Int, Int)]()

for i in 0..<m {
var strength = 0
for j in 0..<n {
if mat[i][j] == 0 { break }
strength += 1
}
pairs.append((strength, i))
}

pairs.sort { $0.0 == $1.0 ? $0.1 < $1.1 : $0.0 < $1.0 }

var indexes = [Int]()
for i in 0..<k {
indexes.append(pairs[i].1)
}
return indexes
}
}


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

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

Пример:
Input: head = [-10,-3,0,5,9]
Output: [0,-3,9,-10,null,5]
Explanation: One possible answer is [0,-3,9,-10,null,5], which represents the shown height balanced BST.


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

1⃣Поскольку нам дан односвязный список, а не массив, у нас нет прямого доступа к элементам списка по индексам. Нам нужно определить средний элемент односвязного списка. Мы можем использовать подход с двумя указателями для нахождения среднего элемента списка. В основном, у нас есть два указателя: slow_ptr и fast_ptr. slow_ptr перемещается на один узел за раз, тогда как fast_ptr перемещается на два узла за раз. К тому времени, как fast_ptr достигнет конца списка, slow_ptr окажется в середине списка. Для списка с четным количеством элементов любой из двух средних элементов может стать корнем BST.

2⃣Как только мы находим средний элемент списка, мы отсоединяем часть списка слева от среднего элемента. Мы делаем это, сохраняя prev_ptr, который указывает на узел перед slow_ptr, т.е. prev_ptr.next = slow_ptr. Для отсоединения левой части мы просто делаем prev_ptr.next = None.

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

😎 Решение:
class Solution {
func sortedListToBST(_ head: ListNode?) -> TreeNode? {
if head == nil { return nil }
var mid = findMiddleElement(head)
var node = TreeNode(mid!.val)
if head === mid { return node }
node.left = sortedListToBST(head)
node.right = sortedListToBST(mid?.next)
return node
}

func findMiddleElement(_ head: ListNode?) -> ListNode? {
var prevPtr: ListNode? = nil
var slowPtr = head
var fastPtr = head
while fastPtr != nil && fastPtr!.next != nil {
prevPtr = slowPtr
slowPtr = slowPtr!.next
fastPtr = fastPtr!.next!.next
}
if prevPtr != nil { prevPtr!.next = nil }
return slowPtr
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 957. Prison Cells After N Days
Сложность: medium

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

Вам дан целочисленный массив cells, где cells[i] == 1, если i-я камера занята, и cells[i] == 0, если i-я камера пуста, и вам дано целое число n.
Верните состояние тюрьмы после n дней (т.е. после n таких изменений, описанных выше).

Пример:
Input: cells = [0,1,0,1,1,0,0,1], n = 7
Output: [0,0,1,1,0,0,0,0]
Explanation: The following table summarizes the state of the prison on each day:
Day 0: [0, 1, 0, 1, 1, 0, 0, 1]
Day 1: [0, 1, 1, 0, 0, 0, 0, 0]
Day 2: [0, 0, 0, 0, 1, 1, 1, 0]
Day 3: [0, 1, 1, 0, 0, 1, 0, 0]
Day 4: [0, 0, 0, 0, 0, 1, 0, 0]
Day 5: [0, 1, 1, 1, 0, 1, 0, 0]
Day 6: [0, 0, 1, 0, 1, 1, 0, 0]
Day 7: [0, 0, 1, 1, 0, 0, 0, 0]


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

1⃣Преобразуйте текущее состояние камер в целое число с помощью битовой маски. Это позволит удобно отслеживать повторяющиеся состояния.

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

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

😎 Решение:
class Solution {
func cellsToBitmap(_ cells: [Int]) -> Int {
var stateBitmap = 0
for cell in cells {
stateBitmap = (stateBitmap << 1) | cell
}
return stateBitmap
}

func nextDay(_ cells: [Int]) -> [Int] {
var newCells = Array(repeating: 0, count: cells.count)
for i in 1..<cells.count-1 {
newCells[i] = (cells[i-1] == cells[i+1]) ? 1 : 0
}
return newCells
}

func prisonAfterNDays(_ cells: [Int], _ N: Int) -> [Int] {
var cells = cells
var N = N
var seen = [Int: Int]()
var isFastForwarded = false

while N > 0 {
if !isFastForwarded {
let stateBitmap = cellsToBitmap(cells)
if let cycleLength = seen[stateBitmap] {
N %= cycleLength - N
isFastForwarded = true
} else {
seen[stateBitmap] = N
}
}

if N > 0 {
N -= 1
cells = nextDay(cells)
}
}
return cells
}
}


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

Дано целое число n, верните количество конечных нулей в n!.

Обратите внимание, что n! = n * (n - 1) * (n - 2) * ... * 3 * 2 * 1.

Пример:
Input: n = 3
Output: 0
Explanation: 3! = 6, no trailing zero.


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

1⃣Вычислите факториал n:
Инициализируйте переменную nFactorial значением 1.
Для каждого i от 2 до n включительно умножайте nFactorial на i.

2⃣Подсчитайте количество конечных нулей в nFactorial:
Инициализируйте переменную zeroCount значением 0.
Пока nFactorial делится на 10 без остатка, делите его на 10 и увеличивайте zeroCount на 1.

3⃣Верните значение zeroCount как количество конечных нулей в n!.

😎 Решение:
func trailingZeroes(_ n: Int) -> Int {
var nFactorial = 1
for i in 2...n {
nFactorial *= i
}

var zeroCount = 0
var nFactorialBigInt = nFactorial

while nFactorialBigInt % 10 == 0 {
nFactorialBigInt /= 10
zeroCount += 1
}

return zeroCount
}


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

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

Пример:
Input: arr = [1,2,2,1,1,3]
Output: true
Explanation: The value 1 has 3 occurrences, 2 has 2 and 3 has 1. No two values have the same number of occurrences.


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

1⃣Сохраните частоты элементов массива arr в хэш-таблице freq.

2⃣Итерируйтесь по хэш-таблице freq и вставьте частоты всех уникальных элементов массива arr в хэш-набор freqSet.

3⃣Верните true, если размер хэш-набора freqSet равен размеру хэш-таблицы freq, иначе верните false.

😎 Решение:
class Solution {
func uniqueOccurrences(_ arr: [Int]) -> Bool {
var freq: [Int: Int] = [:]
for num in arr {
freq[num, default: 0] += 1
}
let freqSet = Set(freq.values)
return freq.count == freqSet.count
}
}


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