Swift на LeetCode: с чего начать и как не бросить
Каждый iOS-разработчик рано или поздно упирается в стену: проекты делаешь, приложения в AppStore отправляешь, а на собеседовании дают задачу про бинарное дерево, и ты впадаешь в ступор. Знакомо? LeetCode — это тот самый тренажёр, который превращает «я просто пишу UI» в «я мыслю алгоритмами». Но есть нюанс: большинство решений в интернете написаны на Python или Java, а Swift — язык со своей философией. В этой статье мы разберём, как эффективно решать задачи на LeetCode именно на Swift, какие структуры данных использовать и на что обратить внимание, чтобы не терять время.
Эта статья для тех, кто уже знает основы Swift, но хочет прокачать алгоритмическое мышление. Мы не будем учить синтаксис — мы будем учиться думать.
Почему Swift — отличный выбор для LeetCode
Многие думают, что Swift — это только UIKit и SwiftUI, но на самом деле язык обладает мощными возможностями для алгоритмических задач:
- Опциональные типы — безопасная работа с nil без неожиданных крашей.
- Value types (структуры) — предсказуемое копирование, никаких сюрпризов с ссылками.
- Ленивые вычисления и функциональные подходы — map, filter, reduce сильно упрощают код.
- Скорость — Swift компилируется в нативный код, часто обгоняя Python в 2-3 раза.
Однако есть и подводные камни: например, массивы в Swift — это структуры, и копирование большого массива может быть дорогим. Но об этом мы поговорим отдельно.
Структуры данных: что нужно знать для Swift на LeetCode
Прежде чем кидаться решать задачи, убедитесь, что вы уверенно работаете с базовыми структурами. Вот список must-know для Swift:
Массивы и срезы (Array, ArraySlice)
Массивы в Swift — это не просто списки, а мощные структуры с O(1) доступом по индексу. Но будьте аккуратны: вставка/удаление в начале — O(n). Используйте ArraySlice, чтобы не копировать данные при работе с подмассивами.
let numbers = [1, 2, 3, 4, 5]
let slice = numbers[1..<4] // ArraySlice, без копирования
print(slice) // [2, 3, 4]
Словари и множества (Dictionary, Set)
Для задач на подсчёт частот, поиск дубликатов или двухпроходные алгоритмы — это ваши лучшие друзья. Помните: Dictionary — это хеш-таблица, O(1) в среднем, но может деградировать до O(n) при плохой хеш-функции.
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var dict = [Int: Int]()
for (i, num) in nums.enumerated() {
if let j = dict[target - num] {
return [j, i]
}
dict[num] = i
}
return []
}
Стек и очередь (Stack, Queue)
В Swift нет встроенных структур стека и очереди, но их легко эмулировать массивом. Для стека — append и popLast. Для очереди используйте ArraySlice или двусвязный список, чтобы избежать O(n) при dequeue.
struct Stack<T> {
private var array = [T]()
mutating func push(_ element: T) { array.append(element) }
mutating func pop() -> T? { array.popLast() }
var isEmpty: Bool { array.isEmpty }
}
Связные списки (Linked List)
LeetCode любит задачи на односвязные списки. В Swift нет встроенной реализации, но вы можете определить класс:
class ListNode {
var val: Int
var next: ListNode?
init(_ val: Int) { self.val = val; self.next = nil }
}
Это классика для задач на разворот списка, обнаружение цикла или удаление узла.
Алгоритмические паттерны для Swift-задач
Чтобы не изобретать велосипед каждый раз, запомните несколько шаблонов, которые покрывают 80% задач на LeetCode.
Два указателя (Two Pointers)
Идеально для отсортированных массивов. Один указатель в начале, другой в конце — двигаем их навстречу друг другу. Пример: задача «Container With Most Water».
func maxArea(_ height: [Int]) -> Int {
var left = 0, right = height.count - 1
var maxWater = 0
while left < right {
let area = min(height[left], height[right]) * (right - left)
maxWater = max(maxWater, area)
if height[left] < height[right] {
left += 1
} else {
right -= 1
}
}
return maxWater
}
Скользящее окно (Sliding Window)
Когда нужно найти подстроку или подмассив с определёнными свойствами. Используйте два указателя, которые двигаются в одном направлении, расширяя и сужая окно.
func lengthOfLongestSubstring(_ s: String) -> Int {
var charSet = Set()
var left = 0, maxLength = 0
let chars = Array(s)
for right in 0..<chars.count {
while charSet.contains(chars[right]) {
charSet.remove(chars[left])
left += 1
}
charSet.insert(chars[right])
maxLength = max(maxLength, right - left + 1)
}
return maxLength
}
Бинарный поиск (Binary Search)
Классика для отсортированных массивов. В Swift есть встроенный binarySearch для диапазонов, но на LeetCode часто нужно писать вручную.
func searchInsert(_ nums: [Int], _ target: Int) -> Int {
var left = 0, right = nums.count - 1
while left <= right {
let mid = left + (right - left) / 2
if nums[mid] == target { return mid }
if nums[mid] < target { left = mid + 1 }
else { right = mid - 1 }
}
return left
}
DFS и BFS для графов и деревьев
Swift отлично подходит для рекурсивных решений, но помните про стек вызовов: глубокая рекурсия может привести к stack overflow. Используйте итеративные подходы с явным стеком или очередью.
func inorderTraversal(_ root: TreeNode?) -> [Int] {
var result = [Int]()
var stack = [TreeNode]()
var current = root
while current != nil || !stack.isEmpty {
while let node = current {
stack.append(node)
current = node.left
}
current = stack.removeLast()
result.append(current!.val)
current = current?.right
}
return result
}
Типичные ошибки новичков в Swift на LeetCode
Даже опытные iOS-разработчики делают эти ошибки, когда переходят на алгоритмические задачи:
- Забывают про опциональные типы — работа с
TreeNode?илиListNode?