Решение: Valid Parentheses на Swift
Основное решение со стеком
func isValid(_ s: String) -> Bool { var stack: [Character] = [] let pairs: [Character: Character] = [ ")": "(", "}": "{", "]": "[" ]
for char in s {
if pairs.keys.contains(char) {
if stack.isEmpty || stack.removeLast() != pairs[char] {
return false
}
} else {
stack.append(char)
}
}
return stack.isEmpty
}
Объяснение алгоритма
Логика: Используем стек для хранения открывающих скобок. При встречи открывающей скобки добавляем в стек. При встречи закрывающей скобки проверяем, что последняя открывающая скобка совпадает по типу. В конце стек должен быть пуст.
Сложность: O(n) время, O(n) память в худшем случае (только открывающие скобки).
Пошаговый пример: "([)]"
Решение
Архитектура проекта (MVVM + Core Data)
TodoApp/
├── Core/
│ ├── CoreData/
│ │ ├── CoreDataStack.swift
│ │ ├── TaskEntity+CoreDataClass.swift
│ │ ├── TaskEntity+CoreDataProperties.swift
│ │ └── Todo.xcdatamodeld
│ ├── Services/
│ │ ├── TaskService.swift
│ │ └── ImageService.swift
├── Models/
│ ├── Task.swift
│ └── Category.swift
├── ViewModels/
│ ├── TaskListViewModel.swift
│ ├── TaskDetailViewModel.swift
│ └── TaskCell.swift
├── Views/
│ ├── TaskListViewController.swift
│ ├── TaskDetailViewController.swift
│ ├── TaskCell.swift
│ └── CategoryCell.swift
└── Resources/
└── Todo.xcdatamodeld
Core Data Setup
Core/CoreData/CoreDataStack.swift:
import CoreData
Решение
Подход
Это классическая задача на практику условных операторов и операции остатка от деления. Ключевой момент — правильный порядок проверок: сначала нужно проверить делимость и на 3, и на 5 одновременно, потом на 3, затем на 5, и только потом возвращать само число.
Основное решение
func fizzBuzz(_ n: Int) -> [String] {
var result: [String] = []
for i in 1...n {
if i % 3 == 0 && i % 5 == 0 {
result.append("FizzBuzz")
} else if i % 3 == 0 {
result.append("Fizz")
} else if i % 5 == 0 {
result.append("Buzz")
} else {
result.append(String(i))
}
}
return result
}
Элегантное решение со строкой
Альтернативный подход — построить строку из условных частей:
Решение
Подход
Это самая популярная задача на собеседованиях. Ключ к оптимальному решению — использовать HashMap (Dictionary в Swift) для хранения значений, которые мы уже видели.
Идея простая: для каждого элемента мы проверяем, есть ли в словаре его "дополнение" (target - current_number). Если да — нашли пару. Если нет — добавляем текущий элемент в словарь.
Оптимальное решение O(n) время, O(n) память
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var dictionary: [Int: Int] = [:] // value: index
for (index, num) in nums.enumerated() {
let complement = target - num
if let complementIndex = dictionary[complement] {
return [complementIndex, index]
}
dictionary[num] = index
}
return [] // Гарантированно не достигнем эту строку по условию задачи
}
Решение
Подход
Это классическая задача на побитовые операции. Ключевая идея основана на том, что степени двойки в двоичном представлении всегда имеют ровно один бит, установленный в 1, а все остальные биты равны 0.
Примеры в двоичной системе:
110100100010000Оптимальное решение O(1)
func isPowerOfTwo(_ n: Int) -> Bool {
return n > 0 && (n & (n - 1)) == 0
}
Как это работает:
Битовое выражение n & (n - 1) удаляет самый правый установленный бит из числа n.
Пример с 16 (binary: 10000):
n = 16 → 10000 (binary)
n - 1 = 15 → 01111 (binary)
n & (n-1) = 0 → 00000 (binary)
Для степени двойки (один бит установлен):
n & (n - 1) всегда даёт 0Для не-степени двойки (несколько битов установлены):
n & (n - 1) даёт ненулевое значениеРешение
Оптимальное решение O(n log n) время
func mergeIntervals(_ intervals: [[Int]]) -> [[Int]] {
guard !intervals.isEmpty else { return [] }
// Сортируем по начальной точке
let sorted = intervals.sorted { $0[0] < $1[0] }
var merged = [sorted[0]]
for i in 1..<sorted.count {
let current = sorted[i]
let last = merged[merged.count - 1]
// Если текущий интервал пересекается с последним объединённым
if current[0] <= last[1] {
// Объединяем: расширяем конец последнего интервала
merged[merged.count - 1] = [last[0], max(last[1], current[1])]
} else {
// Нет пересечения, добавляем новый интервал
merged.append(current)
}
}
return merged
}
Пошаговое выполнение для примера 1:
Входные данные: [[1,3], [2,6], [8,10], [15,18]]
Шаг 1: Сортировка
Отсортировано: [[1,3], [2,6], [8,10], [15,18]]
Решение
Подход
Эта задача кажется очень похожей на сортировку (O(n log n)), но есть оптимальное O(n) решение, использующее HashSet.
Ключевая идея: вместо проверки всех чисел, мы начинаем считать последовательность только с тех чисел, которые являются началом последовательности (т.е. n-1 не существует в множестве).
Оптимальное решение O(n) время, O(n) память
Решение
Архитектура проекта
InstagramClone/
├── Models/
│ ├── Photo.swift
│ └── APIModels.swift
├── Network/
│ ├── PexelsAPIClient.swift
│ ├── ImageCache.swift
│ └── PaginationManager.swift
├── ViewModels/
│ ├── PhotoFeedViewModel.swift
│ └── PhotoDetailViewModel.swift
├── Views/
│ ├── PhotoFeedViewController.swift
│ ├── PhotoCell.swift
│ ├── PhotoDetailViewController.swift
│ └── LoadingPlaceholder.swift
└── Utilities/
├── UIImageView+Async.swift
└── Constants.swift
Domain Models
Models/Photo.swift:
import Foundation
struct Photo: Identifiable, Codable {
let id: Int
let width: Int
let height: Int
let url: String
let photographer: String
let src: PhotoSrc
var aspectRatio: CGFloat {
CGFloat(height) / CGFloat(width)
}
}
struct PhotoSrc: Codable {
let tiny: String
let small: String
let medium: String
let large: String
let original: String
}
Решение
Подход
Для проверки палиндрома со сложностью O(1) по памяти нужно использовать двухпоинтерный подход (two-pointer technique). Сначала отфильтруем только буквы и цифры, затем сравниваем символы с обоих концов, двигаясь к центру.
Основное решение
func isPalindrome(_ s: String) -> Bool {
let filtered = s.lowercased()
.filter { $0.isLetter || $0.isNumber }
let chars = Array(filtered)
var left = 0
var right = chars.count - 1
while left < right {
if chars[left] != chars[right] {
return false
}
left += 1
right -= 1
}
return true
}
Оптимизированное решение без массива
Если нужна истинная O(1) память (без создания массива):
Решение
Архитектура проекта
Использую MVVM с Combine для реактивного программирования и debounce.
iTunesSearch/
├── Models/
│ ├── Track.swift
│ └── APIModels.swift
├── Network/
│ ├── iTunesAPIClient.swift
│ └── ImageCache.swift
├── ViewModels/
│ ├── SearchViewModel.swift
│ └── PlayerViewModel.swift
├── Views/
│ ├── SearchViewController.swift
│ ├── TrackCell.swift
│ ├── PlayerViewController.swift
│ └── LoadingIndicator.swift
└── Utilities/
└── Constants.swift
Domain Models
Models/Track.swift:
import Foundation
Решение
Архитектура и структура проекта
Использую MVVM с Coordinators — это идеальный баланс между простотой и масштабируемостью для iOS приложений среднего размера.
Решение
Подход
Для проверки BST нужно убедиться, что для каждого узла выполняются ограничения: значение больше всех элементов в левом поддереве и меньше всех в правом. Ключевая идея — отслеживать диапазон допустимых значений для каждого узла.
Структура узла дерева
public class TreeNode {
public var val: Int
public var left: TreeNode?
public var right: TreeNode?
public init(_ val: Int) {
self.val = val
self.left = nil
self.right = nil
}
}
Оптимальное решение (O(1) память, O(n) время)
func isValidBST(_ root: TreeNode?) -> Bool {
return isValidBSTHelper(root, min: Int.min, max: Int.max)
}
Решение
Подход
Это классическая задача на двухпоинтерную технику (two-pointer technique). Суть в том, что мы помещаем один указатель в начало, другой в конец, и свапиваем элементы, двигаясь друг к другу до встречи посередине.
Основное решение O(1) память
func reverseString(_ s: inout [String]) {
var left = 0
var right = s.count - 1
while left < right {
// Обмен элементами
(s[left], s[right]) = (s[right], s[left])
left += 1
right -= 1
}
}
Как это работает:
Альтернативное решение с явным свопом
Решение
Подход
Это одна из классических задач на собеседованиях Google, Amazon и других FAANG компаний. Ключевая идея — использовать сам массив как хеш-таблицу, помечая посещённые индексы через изменение знака значений.
Основное решение (O(1) память)
func findDuplicate(_ nums: inout [Int]) -> Int? {
for num in nums {
let index = abs(num) - 1 // Число от 1 до N, индекс от 0 до N-1
// Если значение уже отрицательное, это индекс повторяющегося числа
if nums[index] < 0 {
return abs(num)
}
// Помечаем посещение, меняя знак
nums[index] = -nums[index]
}
return nil
}
Немутирующее решение (если нельзя менять массив)
Если изменение исходного массива недопустимо, используем Floyd's Cycle Detection (алгоритм "черепаха и заяц"):
Решение
Основное решение O(M*N) время, O(1) память
func maxRowSum(_ matrix: [[Int]]) -> Int {
guard !matrix.isEmpty else { return 0 }
var maxSum = Int.min
for row in matrix {
let rowSum = row.reduce(0, +)
maxSum = max(maxSum, rowSum)
}
return maxSum
}
Как это работает:
Решение с использованием map и reduce
func maxRowSumFunctional(_ matrix: [[Int]]) -> Int {
return matrix
.map { $0.reduce(0, +) } // Преобразуем каждую строку в её сумму
.max() ?? Int.min // Находим максимум
}
Поток выполнения:
map { $0.reduce(0, +) } - преобразует каждый массив в сумму его элементов.max() - находит максимальное значение?? Int.min - обработка пустого массиваРешение с явным циклом (более явное)
Решение VIPER архитектуры
Это полное решение экрана выбора услуг с JSON парсингом, UICollectionView сеткой и анимацией.
Архитектура
V (View) → I (Interactor) → P (Presenter) → E (Entity) ← R (Router)
Entities/Service.swift
struct Service: Codable, Identifiable {
let id: String
let name: String
let price: Int
let icon: String
}
struct ServicesResponse: Codable {
let services: [Service]
}
Interactors/ServicesInteractor.swift
protocol ServicesInteractorProtocol {
func fetchServices() async throws -> [Service]
}
Решение
Архитектура проекта (MVVM)
FlightsApp/
├── Models/
│ ├── Flight.swift
│ ├── Airport.swift
│ └── APIModels.swift
├── Core Data/
│ ├── CoreDataStack.swift
│ ├── FavoriteFlight+CoreDataClass.swift
│ ├── FavoriteFlight+CoreDataProperties.swift
│ └── Flights.xcdatamodeld
├── Network/
│ └── FlightsAPIClient.swift
├── Services/
│ ├── FlightService.swift
│ └── FavoritesService.swift
├── ViewModels/
│ ├── FlightsListViewModel.swift
│ └── FlightDetailViewModel.swift
├── Views/
│ ├── FlightsListViewController.swift
│ ├── FlightCell.swift
│ ├── FlightDetailViewController.swift
│ └── LoadingCell.swift
└── Utilities/
└── DateFormatter+Extensions.swift
Domain Models
Models/Flight.swift:
import Foundation