Irbisa · Шпаргалка

Live coding на Kotlin

Хеширование, два указателя, скользящее окно, стек, бинарный поиск, кучи, графы и задачи на корутины вроде ограничения параллелизма

Middle-разработчик · 18 вопросов

  1. 01

    Что такое нотация Big-O и сколько стоит contains у List, у HashSet и у отсортированного списка с binarySearch?

    Big-O описывает, как растут время работы или память с ростом размера входа n, без учёта постоянных множителей.

    fun main() {    val ids = List(100_000) { it * 2 }           // sorted: 0, 2, 4, ...    val idSet = ids.toHashSet()                   // O(n) to build, once     println(199_998 in ids)                       // O(n): compares element by element    println(199_998 in idSet)                     // O(1) on average: hash, then one bucket…
  2. 02

    Дан IntArray и целевое число: верните индексы двух чисел, которые в сумме дают его. Как перейти от O(n²) к O(n)?

    Перебор проверяет все пары, O(n²).

    // Brute force: every pair, O(n²) time, O(1) memoryfun twoSumSlow(nums: IntArray, target: Int): Pair<Int, Int>? {    for (i in nums.indices) for (j in i + 1 until nums.size) {        if (nums[i] + nums[j] == target) return i to j    }    return null…
  3. 03

    Проверьте, является ли строка палиндромом, без учёта регистра и всего, что не буква и не цифра, не создавая перевёрнутую копию.

    Два индекса идут навстречу друг другу с обоих концов: пропускают каждый символ, для которого isLetterOrDigit() ложно, сравнивают остальные через lowercaseChar() и останавливаются на первом несовпадении.

    fun isPalindrome(s: String): Boolean {    var i = 0    var j = s.lastIndex    while (i < j) {        when {            !s[i].isLetterOrDigit() -> i++           // skip spaces and punctuation…
  4. 04

    Проверьте, сбалансированы ли скобки ()[]{} в строке. Какую структуру данных вы возьмёте и сколько это стоит?

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

    private val opening = mapOf(')' to '(', ']' to '[', '}' to '{') fun isBalanced(s: String): Boolean {    val stack = ArrayDeque<Char>()                  // kotlin.collections.ArrayDeque    for (c in s) {        when (c) {…
  5. 05

    Сожмите строку кодированием длин серий (RLE) за один проход, чтобы AAABCCD превратилась в A3BC2D.

    Пройдите строку один раз, считая, сколько раз подряд повторяется текущий символ.

    fun rle(s: String): String {    if (s.isEmpty()) return s    val out = StringBuilder()    var count = 1    for (i in 1..s.length) {                    // one step past the end flushes the last run        if (i < s.length && s[i] == s[i - 1]) {…
  6. 06

    Разверните односвязный список на месте. Напишите итеративную версию, затем скажите, во что обходится рекурсивная.

    Идите по списку с prev, cur и сохранённым next: запомните cur.next, направьте cur.next назад на prev, затем сдвиньте оба вперёд.

    class Node(val value: Int, var next: Node? = null) // Iterative: O(n) time, O(1) extra memoryfun reverse(head: Node?): Node? {    var prev: Node? = null    var cur = head…
  7. 07

    Какова временная сложность этой функции и как довести её до O(n)?

    data class Post(val id: Long, val tags: List<String>) // posts: 10,000 posts with 5 tags each; followed: 2,000 tagsfun followedTags(posts: List<Post>, followed: List<String>): List<String> {    val result = mutableListOf<String>()    for (post in posts) {        for (tag in post.tags) {            if (tag in followed && tag !in result) result.add(tag)        }    }    return result}

    Её сложность O(T · F): для каждого из T тегов tag in followed просматривает весь список followed, а tag !in result ещё и result.

  8. 08

    Сгруппируйте список слов по анаграммам. Что вы используете как ключ мапы и сколько стоит всё решение?

    Возьмите ключ, одинаковый для всех анаграмм: отсортированные буквы слова, так что eat, tea и ate дают aet, а остальное сделает groupBy.

    // Key = the sorted letters: O(n · k log k) for n words of length kfun groupAnagrams(words: List<String>): List<List<String>> =    words.groupBy { word ->        String(word.toCharArray().apply { sort() })   // "tea" and "eat" both give "aet"    }.values.toList()…
  9. 09

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

    Держите окно [start, end] без повторов и мапу из каждого символа в индекс, где он встречался в последний раз.

    fun longestUniqueRun(s: String): Int {    val lastSeen = HashMap<Char, Int>()   // character -> index where it was last seen    var start = 0                          // the window is s[start..end]    var best = 0    for (end in s.indices) {        val prev = lastSeen[s[end]]…
  10. 10

    Найдите непрерывный подмассив с максимальной суммой за O(n) и верните его границы, а не только сумму.

    Алгоритм Кадане: один проход, в котором хранится сумма лучшего отрезка, заканчивающегося на текущем индексе.

    data class Best(val sum: Long, val from: Int, val to: Int)   // to is inclusive fun maxSubarray(a: IntArray): Best {    require(a.isNotEmpty()) { "an empty array has no subarray" }    var best = Best(a[0].toLong(), 0, 0)    var sum = 0L                     // Long: a million large Ints overflow an Int…
  11. 11

    Объедините пересекающиеся интервалы, например слоты бронирования [1,3] [2,6] [8,10], и объясните, зачем сначала сортировать.

    Отсортируйте по началу, затем пройдите один раз: если следующий интервал начинается раньше, чем кончается последний объединённый, продлите конец последнего до большего из двух концов; иначе начните новый блок.

    fun merge(intervals: List<IntRange>): List<IntRange> {    if (intervals.isEmpty()) return emptyList()    val sorted = intervals.sortedBy { it.first }   // O(n log n): overlaps become neighbours    val result = mutableListOf(sorted[0])    for (i in 1 until sorted.size) {        val next = sorted[i]…
  12. 12

    Найдите баг: этот бинарный поиск первого вхождения target на некоторых входных данных зависает навсегда.

    // Returns the index of the first `target` in a sorted array, or -1fun firstIndexOf(a: IntArray, target: Int): Int {    var lo = 0    var hi = a.size - 1    while (lo < hi) {        val mid = (lo + hi) / 2        if (a[mid] < target) lo = mid else hi = mid    }    return if (a.isNotEmpty() && a[lo] == target) lo else -1} // firstIndexOf(intArrayOf(1, 2, 2, 2, 5), 2) never returns

    lo = mid не сужает диапазон: как только hi == lo + 1, mid совпадает с lo, и если a[mid] < target, цикл бесконечно присваивает lo то же значение.

  13. 13

    Верните k самых частых элементов массива. Когда использовать PriorityQueue, а когда сортировку по корзинам (bucket sort)?

    Сначала посчитайте частоты мапой (groupingBy { it }.eachCount()), затем выберите k лучших: min-heap размера k даёт O(n + m log k) для m различных значений, а bucket sort по частоте даёт O(n), потому что ни одна частота не превышает n.

    // Heap: O(n + m log k) for m distinct valuesfun topKFrequent(nums: IntArray, k: Int): List<Int> {    val counts = nums.asList().groupingBy { it }.eachCount()   // value -> count, O(n)    val heap = PriorityQueue<Map.Entry<Int, Int>>(compareBy { it.value })   // min-heap    for (entry in counts.entries) {        heap.add(entry)…
  14. 14

    Посчитайте острова в сетке из 0 и 1. BFS или DFS, и что ломается при глубокой рекурсии на большой сетке?

    Обойдите все клетки; каждая непосещённая клетка суши начинает новый остров, который вы заливаете обходом в ширину (BFS) или в глубину (DFS), помечая всю его сушу посещённой.

    private val steps = listOf(1 to 0, -1 to 0, 0 to 1, 0 to -1) fun countIslands(grid: Array<IntArray>): Int {    val rows = grid.size    val cols = grid.firstOrNull()?.size ?: return 0    val seen = Array(rows) { BooleanArray(cols) }   // or flip 1 to 0 if the input may change…
  15. 15

    В каком порядке этот код печатает буквы от A до E и почему A выводится не первой?

    fun main() {    runBlocking {        launch { println("A") }        println("B")        coroutineScope {            launch {                delay(100)                println("C")            }            println("D")        }        println("E")    }}

    Он печатает B, D, A, C, E.

  16. 16

    Скачайте 100 картинок так, чтобы одновременно выполнялось не больше 4 запросов. Напишите это на корутинах и верните результаты в порядке входного списка.

    Запустите по одному async на URL внутри coroutineScope, оберните каждый запрос в withPermit { } у Semaphore(4) и закончите awaitAll().

    suspend fun <T> downloadAll(    urls: List<String>,    maxInFlight: Int = 4,    load: suspend (String) -> T,): List<T> = coroutineScope {    val permits = Semaphore(maxInFlight)…
  17. 17

    По списку Gradle-модулей и их зависимостей верните порядок сборки или сообщите о цикле, из-за которого собрать нельзя.

    Это топологическая сортировка ориентированного графа.

    sealed interface BuildPlan {    data class Order(val modules: List<String>) : BuildPlan    data class Cycle(val path: List<String>) : BuildPlan} // deps: module -> the modules it depends on (they must be built first)…
  18. 18

    Запросите два зеркала параллельно, верните первый успешный ответ, отмените второй запрос и через 2 секунды откатитесь на кэш. Напишите это на корутинах.

    Запустите оба запроса внутри channelFlow, отправляйте через send каждый успех и глотайте каждую сетевую ошибку, а первое значение берите через firstOrNull(), который отменяет flow и проигравший запрос.

    // The first source that succeeds wins; null if every one of them failssuspend fun <T> firstSuccess(vararg sources: suspend () -> T): T? =    channelFlow {        for (source in sources) launch {            try {                send(source())…