HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>Мои коллеги и приятели нередко проходят интервью в компании типа Facebook. Большинство разработчиков не ожидают, с чем столкнутся на техническом интервью: даже очень опытные разработчики "сыпались" на комбинаторике, структурах данных или битовых операциях.</p>
1 <p>Мои коллеги и приятели нередко проходят интервью в компании типа Facebook. Большинство разработчиков не ожидают, с чем столкнутся на техническом интервью: даже очень опытные разработчики "сыпались" на комбинаторике, структурах данных или битовых операциях.</p>
2 <p>В обычном продакшене программистам не приходится долго быть погруженными в тему алгоритмов и структур данных. Если на вопросы про iOS-платформу все отвечают хорошо, то такие проверки для mobile-инженеров становятся самой сложной частью. Я поделюсь тремя задачами с реальных собеседований в известные во всём мире IT-компании, на которые давалось 40-60 минут.</p>
2 <p>В обычном продакшене программистам не приходится долго быть погруженными в тему алгоритмов и структур данных. Если на вопросы про iOS-платформу все отвечают хорошо, то такие проверки для mobile-инженеров становятся самой сложной частью. Я поделюсь тремя задачами с реальных собеседований в известные во всём мире IT-компании, на которые давалось 40-60 минут.</p>
3 <h2>Задача № 1</h2>
3 <h2>Задача № 1</h2>
4 <p>Начнём с простой, но классической задачки, которая попадалась даже мне на собеседованиях в российские крупные IT-компании:</p>
4 <p>Начнём с простой, но классической задачки, которая попадалась даже мне на собеседованиях в российские крупные IT-компании:</p>
5 <p><strong>Условие</strong>: есть массив размера N. Он заполнен натуральными числами от 1 до N. Надо придумать алгоритм, который без использования дополнительных переменных найдет любое повторяющееся число в массиве, если оно там есть.</p>
5 <p><strong>Условие</strong>: есть массив размера N. Он заполнен натуральными числами от 1 до N. Надо придумать алгоритм, который без использования дополнительных переменных найдет любое повторяющееся число в массиве, если оно там есть.</p>
6 <p><strong>Решение</strong>:</p>
6 <p><strong>Решение</strong>:</p>
7 var array = [1,8,9,5,9,6,4,7,5] // naturals numbers are &gt; 0 let N = array.count print("Duplicates:") for i in 1..&lt;N { if array[abs(array[i])-1] &gt;= 0 { array[abs(array[i])-1] = -array[abs(array[i])-1] } else { print(abs(array[i])) } }<p><strong>Объяснение</strong>: у нас до N элементов (числа больше 0, т. к. натуральные), и мы можем по ходу перебора массива помечать изменением знака на "-" значения тех элементов, индекс которых равен встретившемуся при переборе числу.</p>
7 var array = [1,8,9,5,9,6,4,7,5] // naturals numbers are &gt; 0 let N = array.count print("Duplicates:") for i in 1..&lt;N { if array[abs(array[i])-1] &gt;= 0 { array[abs(array[i])-1] = -array[abs(array[i])-1] } else { print(abs(array[i])) } }<p><strong>Объяснение</strong>: у нас до N элементов (числа больше 0, т. к. натуральные), и мы можем по ходу перебора массива помечать изменением знака на "-" значения тех элементов, индекс которых равен встретившемуся при переборе числу.</p>
8 <h2>Задача № 2</h2>
8 <h2>Задача № 2</h2>
9 <p>Вторая задачка про деревья, она была как раз на двухчасовом собеседовании в лондонский офис Facebook. Первый час спрашивали про iOS, во второй час нужно было решить задачу и, как обычно бывает, написать наиболее оптимизированный по процу и памяти алгоритм.</p>
9 <p>Вторая задачка про деревья, она была как раз на двухчасовом собеседовании в лондонский офис Facebook. Первый час спрашивали про iOS, во второй час нужно было решить задачу и, как обычно бывает, написать наиболее оптимизированный по процу и памяти алгоритм.</p>
10 <p><strong>Условие</strong>: на входе подаётся Бинарное Дерево, надо определить, обычное ли это Бинарное Дерево или Бинарное Дерево Поиска?</p>
10 <p><strong>Условие</strong>: на входе подаётся Бинарное Дерево, надо определить, обычное ли это Бинарное Дерево или Бинарное Дерево Поиска?</p>
11 <p><strong>Решение</strong>:</p>
11 <p><strong>Решение</strong>:</p>
12 class Node { let value: Int var left: Node? var right: Node? init(_ value: Int) { self.value = value } } func checkIsBST(_ node: Node?, min: Int, max: Int) -&gt; Bool { guard let n = node else { return true // Empty tree is BST } if min &lt;= n.value &amp;&amp; n.value &lt; max { // Recursion check min/max constraints return checkIsBST(n.left, min: min, max: n.value) &amp;&amp; checkIsBST(n.right, min: n.value, max: max) } return false // Violates the min/max constraints, so it's BT } func isBST(root: Node) -&gt; Bool { return checkIsBST(root, min: Int.min, max: Int.max) } // Tests let treeBST = Node(5) treeBST.left = Node(2) treeBST.right = Node(10) treeBST.left?.left = Node(1) treeBST.left?.right = Node(3) treeBST.right?.left = Node(8) treeBST.right?.right = Node(12) print("isBST \(isBST(root: treeBST))") let treeBT = Node(5) treeBT.left = Node(6) treeBT.right = Node(10) treeBT.left?.left = Node(2) treeBT.left?.right = Node(8) treeBT.right?.left = Node(11) treeBT.right?.right = Node(12) print("isBST \(isBST(root: treeBT))")<p><strong>Объяснение</strong>: эффективность в нашем случае в отличие от обычной рекурсивной проверки (решение "в лоб") достигается тем, что мы создаём дополнительный метод checkIsBST и он рекурсивно проверяет каждый узел лишь единожды за счет наличия аргументов min и max.</p>
12 class Node { let value: Int var left: Node? var right: Node? init(_ value: Int) { self.value = value } } func checkIsBST(_ node: Node?, min: Int, max: Int) -&gt; Bool { guard let n = node else { return true // Empty tree is BST } if min &lt;= n.value &amp;&amp; n.value &lt; max { // Recursion check min/max constraints return checkIsBST(n.left, min: min, max: n.value) &amp;&amp; checkIsBST(n.right, min: n.value, max: max) } return false // Violates the min/max constraints, so it's BT } func isBST(root: Node) -&gt; Bool { return checkIsBST(root, min: Int.min, max: Int.max) } // Tests let treeBST = Node(5) treeBST.left = Node(2) treeBST.right = Node(10) treeBST.left?.left = Node(1) treeBST.left?.right = Node(3) treeBST.right?.left = Node(8) treeBST.right?.right = Node(12) print("isBST \(isBST(root: treeBST))") let treeBT = Node(5) treeBT.left = Node(6) treeBT.right = Node(10) treeBT.left?.left = Node(2) treeBT.left?.right = Node(8) treeBT.right?.left = Node(11) treeBT.right?.right = Node(12) print("isBST \(isBST(root: treeBT))")<p><strong>Объяснение</strong>: эффективность в нашем случае в отличие от обычной рекурсивной проверки (решение "в лоб") достигается тем, что мы создаём дополнительный метод checkIsBST и он рекурсивно проверяет каждый узел лишь единожды за счет наличия аргументов min и max.</p>
13 <h2>Задача № 3</h2>
13 <h2>Задача № 3</h2>
14 <p>Третью задачку могу предложить решить псевдокодом на бумаге, но и в коде она не сильно объёмная. В решении я показываю сам принцип.</p>
14 <p>Третью задачку могу предложить решить псевдокодом на бумаге, но и в коде она не сильно объёмная. В решении я показываю сам принцип.</p>
15 <p><strong>Условие</strong>: в массиве известной длины есть целое число, которое встречается больше 50% раз. Надо найти это число. Решить за один проход и минимальное использование памяти.</p>
15 <p><strong>Условие</strong>: в массиве известной длины есть целое число, которое встречается больше 50% раз. Надо найти это число. Решить за один проход и минимальное использование памяти.</p>
16 <p><strong>Решение</strong>:</p>
16 <p><strong>Решение</strong>:</p>
17 // Utils for bit operations enum Bit { case zero, one func asInt() -&gt; Int { self == .one ? 1 : 0} } // to bytes(UInt8) func bitsToBytes(bits: [Bit]) -&gt; [UInt8] { let numBits = bits.count let numBytes = (numBits + 7)/8 var bytes = [UInt8](repeating: 0, count: numBytes) for (index, bit) in bits.enumerated() { if bit == .one { bytes[index / 8] += UInt8(1 &lt;&lt; (7 - index % 8)) } } return bytes } // to bits extension BinaryInteger { var bits: Array&lt;Bit&gt; { var arr = [Bit](repeatElement(.zero, count: self.bitWidth)) var n = self for i in 1...self.bitWidth { if n &amp; 1 == 1 { arr[self.bitWidth-i] = .one } n &gt;&gt;= 1 } return arr } } // Test data, generate numOfElements (most frequent integer is N) let N = 9873 // Most frequent number let numOfElements = 1001 // use odd numbers var ints = [Int]() for i in 1...numOfElements { i % 2 == 0 ? ints.append(Int.random(in: Int.min...Int.max)) : ints.append(N) } print("\(N) count \(ints.filter { $0 == N }.count)/\(numOfElements)") // Solution for most frequent(50%+) number var bit64Counter = [Int](repeatElement(0, count: 64)) // for 64-bit int for i in ints { let bits = i.bits bit64Counter = zip(bit64Counter, bits).map { $0.0 + $0.1.asInt() } } let bitsOfMostNum:[Bit] = bit64Counter.map { $0 &gt; (Int(numOfElements/2)) ? .one : .zero } let bytesOfMostNum:[UInt8] = bitsToBytes(bits: bitsOfMostNum) let mostNum = UnsafeRawPointer(bytesOfMostNum).assumingMemoryBound(to: Int.self).pointee.bigEndian print("Most frequent number \(mostNum)")<p><strong>Объяснение</strong>: делаем счётчик на каждый бит (на 64 бита - 64 счетчика). Каждое число представляем в бинарном виде и считаем, сколько встречается единиц (1). После окончания работы цикла 64 счётчика превращаем в биты (если значение счётчика больше половины длины массива, то ставим 1, иначе - 0) и получаем наиболее часто встречающееся число.</p>
17 // Utils for bit operations enum Bit { case zero, one func asInt() -&gt; Int { self == .one ? 1 : 0} } // to bytes(UInt8) func bitsToBytes(bits: [Bit]) -&gt; [UInt8] { let numBits = bits.count let numBytes = (numBits + 7)/8 var bytes = [UInt8](repeating: 0, count: numBytes) for (index, bit) in bits.enumerated() { if bit == .one { bytes[index / 8] += UInt8(1 &lt;&lt; (7 - index % 8)) } } return bytes } // to bits extension BinaryInteger { var bits: Array&lt;Bit&gt; { var arr = [Bit](repeatElement(.zero, count: self.bitWidth)) var n = self for i in 1...self.bitWidth { if n &amp; 1 == 1 { arr[self.bitWidth-i] = .one } n &gt;&gt;= 1 } return arr } } // Test data, generate numOfElements (most frequent integer is N) let N = 9873 // Most frequent number let numOfElements = 1001 // use odd numbers var ints = [Int]() for i in 1...numOfElements { i % 2 == 0 ? ints.append(Int.random(in: Int.min...Int.max)) : ints.append(N) } print("\(N) count \(ints.filter { $0 == N }.count)/\(numOfElements)") // Solution for most frequent(50%+) number var bit64Counter = [Int](repeatElement(0, count: 64)) // for 64-bit int for i in ints { let bits = i.bits bit64Counter = zip(bit64Counter, bits).map { $0.0 + $0.1.asInt() } } let bitsOfMostNum:[Bit] = bit64Counter.map { $0 &gt; (Int(numOfElements/2)) ? .one : .zero } let bytesOfMostNum:[UInt8] = bitsToBytes(bits: bitsOfMostNum) let mostNum = UnsafeRawPointer(bytesOfMostNum).assumingMemoryBound(to: Int.self).pointee.bigEndian print("Most frequent number \(mostNum)")<p><strong>Объяснение</strong>: делаем счётчик на каждый бит (на 64 бита - 64 счетчика). Каждое число представляем в бинарном виде и считаем, сколько встречается единиц (1). После окончания работы цикла 64 счётчика превращаем в биты (если значение счётчика больше половины длины массива, то ставим 1, иначе - 0) и получаем наиболее часто встречающееся число.</p>
18 <p><em>На этом всё, успешных вам собеседований!</em></p>
18 <p><em>На этом всё, успешных вам собеседований!</em></p>
19  
19