PrepBro
Профессии
PrepBro
Профессия:

Подготовка

  • Вопросы2048
  • Задачи17

Аналитика

  • hh статистика
  • Анализ резюме

Практика

  • Тестовое собеседование
  • Mock-собеседование
  • Менторы

Поддержка / отзывы

Telegram админа
Профессия:

Подготовка

  • Вопросы2048
  • Задачи17

Аналитика

  • hh статистика
  • Анализ резюме

Практика

  • Тестовое собеседование
  • Mock-собеседование
  • Менторы

Поддержка / отзывы

Telegram админа
Все 24 профессии
Android DeveloperData AnalystSystem Analyst1С DeveloperiOS DeveloperBusiness AnalystJava DeveloperData ScientistQA EngineerQA AutomationPHP BackendC/C++ BackendDevOps EngineerIT Project ManagerFrontend DeveloperNode.js BackendUnity DeveloperC# BackendProduct AnalystFlutter DeveloperPython DeveloperIT Product ManagerGo DeveloperData Engineer

© 2026 PrepBro. Все права защищены.

Telegram-бот

Задачи по iOS Developer

Valid Parentheses
1.3 Junior🔥 29💬 1

Решение: 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) память в худшем случае (только открывающие скобки).

Пошаговый пример: "([)]"

Читать полностью ->
Приложение со списком задач (Todo)
1.6 Junior🔥 29💬 1

Решение

Архитектура проекта (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
Читать полностью ->
FizzBuzz
1.0 Junior🔥 27💬 1

Решение

Подход

Это классическая задача на практику условных операторов и операции остатка от деления. Ключевой момент — правильный порядок проверок: сначала нужно проверить делимость и на 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
}

Элегантное решение со строкой

Альтернативный подход — построить строку из условных частей:

Читать полностью ->
Two Sum
1.2 Junior🔥 26💬 1

Решение

Подход

Это самая популярная задача на собеседованиях. Ключ к оптимальному решению — использовать 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 Junior🔥 20💬 1

Решение

Подход

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

Примеры в двоичной системе:

  • 1 = 1
  • 2 = 10
  • 4 = 100
  • 8 = 1000
  • 16 = 10000

Оптимальное решение 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) даёт ненулевое значение
Читать полностью ->
Merge Intervals
2.0 Middle🔥 19💬 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]]
Читать полностью ->
Longest Consecutive Sequence
1.8 Middle🔥 18💬 1

Решение

Подход

Эта задача кажется очень похожей на сортировку (O(n log n)), но есть оптимальное O(n) решение, использующее HashSet.

Ключевая идея: вместо проверки всех чисел, мы начинаем считать последовательность только с тех чисел, которые являются началом последовательности (т.е. n-1 не существует в множестве).

Оптимальное решение O(n) время, O(n) память

Читать полностью ->
Простой Instagram (лента изображений)
1.8 Middle🔥 18💬 1

Решение

Архитектура проекта

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
}
Читать полностью ->
Проверка на палиндром
1.3 Junior🔥 18💬 1

Решение

Подход

Для проверки палиндрома со сложностью 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) память (без создания массива):

Читать полностью ->
iTunes поиск музыки
2.2 Middle🔥 17💬 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
Читать полностью ->
Приложение погоды
2.0 Middle🔥 17💬 1

Решение

Архитектура и структура проекта

Использую MVVM с Coordinators — это идеальный баланс между простотой и масштабируемостью для iOS приложений среднего размера.

Читать полностью ->
Бинарное дерево поиска
2.0 Middle🔥 17💬 1

Решение

Подход

Для проверки 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)
}
Читать полностью ->
Reverse String
1.0 Junior🔥 16💬 1

Решение

Подход

Это классическая задача на двухпоинтерную технику (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
    }
}

Как это работает:

  1. left указывает на начало, right на конец
  2. Пока left < right, мы свапиваем элементы
  3. Двигаемся навстречу друг другу
  4. Когда встречаемся (left >= right), готово

Альтернативное решение с явным свопом

Читать полностью ->
Поиск дубликата в массиве
2.0 Middle🔥 15💬 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 (алгоритм "черепаха и заяц"):

Читать полностью ->
Максимальная сумма в массиве
1.0 Junior🔥 14💬 1

Решение

Основное решение 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
}

Как это работает:

  1. Проходим по каждой строке матрицы - O(M)
  2. Для каждой строки вычисляем сумму элементов - O(N)
  3. Отслеживаем максимальную сумму
  4. Общая сложность: O(M*N)

Решение с использованием map и reduce

func maxRowSumFunctional(_ matrix: [[Int]]) -> Int {
    return matrix
        .map { $0.reduce(0, +) }  // Преобразуем каждую строку в её сумму
        .max() ?? Int.min         // Находим максимум
}

Поток выполнения:

  1. map { $0.reduce(0, +) } - преобразует каждый массив в сумму его элементов
  2. .max() - находит максимальное значение
  3. ?? Int.min - обработка пустого массива

Решение с явным циклом (более явное)

Читать полностью ->
UI из макета (Avito Tech)
2.2 Middle🔥 13💬 1

Решение 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]
}
Читать полностью ->
Авиаперелеты (Wildberries)
1.8 Middle🔥 8💬 1

Решение

Архитектура проекта (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
Читать полностью ->