| tags |
|
||||
|---|---|---|---|---|---|
| type | note | ||||
| categorie |
|
The two sum problem is a common interview question, and it is a variation of the subset sum problem -> and see Subset-Sum. There is a popular dynamic programming solution for the subset sum problem, but for the two sum problem we can actually write an algorithm that runs in O(n) time. The challenge is to find all the pairs of two integers in an unsorted array that sum up to a given S.
For example, if the array is [3, 5, 2, -4, 8, 11] and the sum is 7, your program should return [[11, -4], [2, 5]] because 11 + -4 = 7 and 2 + 5 = 7
O(n)
JS:
// our two sum function which will return
// all pairs in the array that sum up to S
function twoSum(arr, S) {
var sums = [];
var hashTable = {};
// check each element in array
for (var i = 0; i < arr.length; i++) {
// calculate S - current element
var sumMinusElement = S - arr[i];
// check if this number exists in hash table
// if so then we found a pair of numbers that sum to S
if (hashTable[sumMinusElement.toString()] !== undefined) {
sums.push([arr[i], sumMinusElement]);
}
// add the current number to the hash table
hashTable[arr[i].toString()] = arr[i];
}
// return all pairs of integers that sum to S
return sums;
}Kotlin:
fun twoSum(list: List<Int>, s: Int): List<Pair<Int, Int>> {
val set = mutableSetOf<Int>()
val result = mutableListOf<Pair<Int, Int>>()
list.forEach { el ->
val sumMinusEl = s - el
if ( set.contains(sumMinusEl)) {
result.add(Pair(el, sumMinusEl))
}
set.add(el)
}
return result
}
twoSum(listOf(3, 5, 2 ,-4, 8, 11), 7) Fins maximus sum of prefix of current array. [] is also prefix of array [1, 2, 0, -4, 4. 6] as [1], [1, 2], [1, 2, 0] ... etc.
O(n)
Kotlin:
val m1 = listOf<Int>(1, 2, 0, 3, -4)
val m2 = listOf<Int>(0, 0, 9, 2)
val m3 = listOf<Int>()
val m4 = listOf<Int>(-1, -2, -3)
fun findMaxSumPrefix(list: List<Int>): Int {
var maxSum = 0
var currentSum = 0
list.forEach { i ->
currentSum += i
maxSum = max(currentSum, maxSum)
}
return maxSum
}
findMaxSumPrefix(m4)There is a string s. You need find the first unique char and return index of this char.
O(n^2)
Kotlin:
val s0 = "hutrihbp"
val s1 = "abcabce"
val s2 = "uuee"
fun findStr(s: String): Int {
return s.indexOfFirst { char ->
s.count { it == char} == 1
}
}
println("result = ${findStr(s1)}")also see solution with set -> O(n)
Kotlin:
// Дан массив целых чисел, в котором каждое число встречается 2 раза,
// и лишь одно число встречается 1 раз. Нужно найти это число
// Решение с set
fun findSingle(list: List<Int>): Int {
val set = mutableSetOf<Int>()
list.forEach { x ->
if (set.contains(x)) {
set.remove(x)
} else {
set.add(x)
}
}
return set.first()
}
// Решение с xor - более эффективное по памяти
fun findSingleXor(list: List<Int>): Int {
return list.reduce { acc, x ->
acc xor x
}
}O(n)
Kotlin:
val s0 = "hutrhbpppppnppp000000fff0fff000faa0t0t0t0rrrrrrrrrtrst00rstst"
val s1 = "abcabce"
val s2 = "uuee"
val s3 = ""
fun findMaxCountTheSameConsecutiveChars(s: String): Int {
var maxIndex: Int
var currentIndex = 0
var result = 0
while (currentIndex < s.length) {
maxIndex = currentIndex
while (maxIndex < s.length && s[currentIndex] == s[maxIndex]) {
++maxIndex
}
result = max(result, maxIndex - currentIndex)
currentIndex = maxIndex
}
return result
}
findMaxCountTheSameConsecutiveChars(s0)Weather exist subset of non-negative array (or elements are in non-decreasing order) where sum of this subset items equals some X
see 2-Sum example
Kotlin:
//Задача. Дан массив целых неотрицательных чисел arr и целое число X.
//Определите, существует ли в массиве такой непрерывный подмассив, что сумма его элементов равна X.
fun isSubArraySum(array: Array<Int>, x: Int): Boolean {
var right = 0
var currentSum = 0
for (left in array.indices) {
if (left > 0) {
currentSum -= array[left - 1]
}
while (right < array.size && currentSum < x) {
currentSum += array[right]
++right
}
if (currentSum == x) return true
}
return false
}
val a1 = arrayOf(1, 2, 3, 4, 5)
val a2 = arrayOf(1, 9, 3, 2, 5)
val a3 = emptyArray<Int>()
isSubArraySum(a2, 14)O(log n)
Kotlin:
// Есть упорядоченный массив целых чисел arr, нужно определить, есть ли в нём число X.
// Сложность мешьше O(n)
fun isXinArray(array: Array<Int>, x: Int): Boolean {
var left = 0
var right = array.size
while (left < right) {
val mid = (left + right) / 2
if (array[mid] == x) {
return true
} else {
if (array[mid] < x) {
left = mid + 1
} else {
right = mid
}
}
}
return false
}
val a1 = arrayOf(1, 2, 33, 334, 555)
val a3 = emptyArray<Int>()
isXinArray(a1, 33)// Этот метод позволяет искать не только элемент массива. Пусть нам нужно найти решение уравнения x⋅log2(x)=Y. Вычислить значение выражения x⋅log2(x) легко, а найти решение уравнения математически — нет. Поскольку функция x⋅log2(x) монотонно возрастает при x⩾1, можно применить бинарный поиск.
double solve_equation(double Y) {
double left = 1;
double right = Y;
for (int i = 0; i < 100; ++i) {
double mid = (left + right) / 2;
double expr_result = mid * log2(mid);
if (expr_result < Y)
left = mid;
else
right = mid;
}
return left;
}Kotlin:
val s1 = "01"
val s2 = "001001010010100101"
val s3 = "00001111"
val s4 = "000001111"
val s5 = "010101010101"
fun findSubStr(s: String): Int {
var left = 0
var right = s.length - 1
while (left < right) {
val mid = (left + right) / 2
if (right == (left + 1)) {
return left
} else {
if (s[mid] == '0') {
left = mid
} else {
right = mid
}
} }
return left
}
findSubStr(s2)// дан упорядоченный массив `arr` и число `X`, нужно найти индекс максимального элемента `arr`, не превосходящего `X`. Если такого элемента не существует, вернуть `-1`.
int max_lower_or_equal(const vector<int> &sorted_arr, int X) {
// Сначала проверим, существует ли искомый элемент
if (sorted_arr.empty() || sorted_arr[0] > X)
return -1;
size_t left_idx = 0;
size_t right_idx = sorted_arr.size();
while (left_idx + 1 < right_idx) {
size_t mid_idx = (left_idx + right_idx) / 2;
if (sorted_arr[mid_idx] <= X)
left_idx = mid_idx;
else
right_idx = mid_idx;
}
return left_idx;
}Kotlin:
fun findIndexOfMaxLower(sortedList: List<Int>, x: Int): Int {
if (sortedList.isEmpty() || sortedList[0] > x) {
return -1
}
var left = 0
var right = sortedList.size
while (left + 1 < right) {
val mid = (left + right) / 2
if (sortedList[mid] <= x) {
left = mid
} else {
right = mid
}
}
return left
}
findIndexOfMaxLower(listOf(-4, 8, 11, 15, 20, 83, 83, 100), 21)
// 4Kotlin:
fun fibonacciN(n: Int) {
var t1 = 0
var t2 = 1
print("First $n terms: ")
for (i in 1..n) {
print("$t1 ")
val sum = t1 + t2
t1 = t2
t2 = sum
}
}
fibonacciN(10)Дано целое число n. Требуется вывести все правильные скобочные последовательности длины 2 * n, упорядоченные лексикографически (см. https://ru.wikipedia.org/wiki/Лексикографический_порядок). В задаче используются только круглые скобки.
Это пример относительно сложной алгоритмической задачи. Будем генерировать последовательность по одному символу; в каждый момент мы можем к текущей последовательности приписать либо открывающую скобку, либо закрывающую. Открывающую скобку можно дописать, если до этого было добавлено менее n открывающих скобок, а закрывающую — если в текущей последовательности количество открывающих скобок превосходит количество закрывающих. Такой алгоритм при аккуратной реализации автоматически гарантирует лексикографический порядок в ответе; работает за время, пропорциональное произведению количества элементов в ответе на n; при этом требует линейное количество дополнительной памяти.
Kotlin:
// Рассмотрим задачу: дано число N, нужно сгенерировать все правильные
// скобочные последовательности из N открывающих и N закрывающих скобок.
// n = N * 2 количество скобок
// mutableList = list(np.zeros(k)) пустой список, куда кладем скобки
// dif = 0 # разница между скобками
// index = 0 # индекс, по которому кладем скобку в список
fun gen(n: Int, dif: Int, index: Int, list: MutableList<Char>) {
// кладем откр. скобку, только если хватает места
if (dif <= n - index - 2) {
list[index] = '('
gen(n, dif + 1, index + 1, list)
}
// закр. скобку можно положить всегда, если dif > 0
if (dif > 0) {
list[index] = ')'
gen(n, dif - 1, index + 1, list)
}
// выходим из цикла и печатаем
if (index == n && dif == 0) println(list)
}
val list = MutableList(8){'0'}
gen(4*2, 0, 0, list)or
fun gen(n: Int, open: Int, closed: Int, index: Int, list: MutableList<Char>) {
// кладем откр. скобку, только если хватает места
if (open < n / 2) {
list[index] = '('
gen(n, open + 1, closed, index + 1, list)
}
// закр. скобку можно положить всегда, если открывающих скобок больше
if ((open - closed) > 0) {
list[index] = ')'
gen(n, open, closed + 1, index + 1, list)
}
// выходим из цикла и печатаем
if (index == n && open == closed) println(list)
}- C++:
std::sort(arr.begin(), arr.end());— Introsort иstd::stable_sort(arr.begin(), arr.end());— сортировка слиянием. - Java:
Arrays.sort(arr);— для примитивных типов быстрая сортировка, для объектов используется сортировка слиянием - Python:
arr.sort()илиsorted_arr = sorted(arr)— Timsort. - JavaScript:
arr.sort()— алгоритм зависит от конкретной реализации
Задача: дан набор слов, стартовое и конечное слово. За один ход можно взять текущее слово и заменить его на любое другое из этого набора, если они отличаются ровно на один символ. Например, дан набор
[”cat”, “cap”, “tab”, “tap”], начальное слово“cat”, конечное —“tap”. Можно совершить цепочку преобразований“cat” → “cap” → “tap”, а
“cap” → “tab” → “tap”нельзя, поскольку“cap”и“tab”отличаются не в одном символе. За какое наименьшее число ходов можно превратить стартовое слово в конечное?
Обычно в задаче требуется сделать что-то из следующего:
- Проверить существование пути из одной вершины в другую или определить, является ли неориентированный граф связным.
- Найти кратчайший путь в невзвешенном графе.
- Найти кратчайший путь во взвешенном графе.
Первая задача решается поиском в глубину или ширину, вторая — поиском в ширину, третья — алгоритмом Дейкстры.
- Preorder: сначала посещаем текущую вершину, затем рассматриваем её поддеревья.
- Inorder: рассматриваем левое поддерево, посещаем текущую вершину и затем рассматриваем правое поддерево. Применим только к бинарным деревьям.
- Postorder: рассматриваем все поддеревья текущей вершины, затем посещаем её.
Примеры:
-
Вывести все ключи двоичного дерева поиска в порядке неубывания. - Inorder
-
Заданы зависимости вида «задача A должна быть выполнена ранее задачи B». Нужно сформировать корректную последовательность выполнения задач. - Preorder
-
Для двух вершин в дереве найти наименьшего общего предка. - Postorder
Kotlin Huffman Tree realisation:
abstract class HuffmanTree(var freq: Int) : Comparable<HuffmanTree> {
override fun compareTo(other: HuffmanTree) = freq - other.freq
}
class HuffmanLeaf(freq: Int, var value: Char) : HuffmanTree(freq)
class HuffmanNode(var left: HuffmanTree, var right: HuffmanTree) : HuffmanTree(left.freq + right.freq)import java.lang.Integer.max
// Дано бинарное дерево со взвешенными узлами
// Найти ветку с максимальной суммой узлов
class Node(
var value: Int,
var left: Node? = null,
var right: Node? = null
)
val tree =
Node(1,
Node(2,
Node(1, Node(9)),
Node(7)
),
Node(9,
Node(5, Node(7)),
Node(2)
)
)
// not very effective method
fun maxBranchSum(tree: Node?): Int {
return when(tree) {
null -> 0
else -> tree.value + max(maxBranchSum(tree.left), maxBranchSum(tree.right))
}
}
maxBranchSum(tree)Задача. Дано бинарное дерево, нужно вывести список списков значений вершин «по слоям». В каждом слое значения должны идти слева направо. Для дерева:
5
/ \
/ \
3 1
\ /
4 2
result [[5], [3, 1], [4, 2]]
for tree
class Node(
var value: Int,
var left: Node? = null,
var right: Node? = null
)
val tree =
Node(1,
Node(2,
Node(1, Node(9)),
Node(7)
),
Node(9,
Node(5, Node(7)),
Node(2)
)
)
fun getResult(root: Node): List<List<Int>> {
val result = mutableListOf<MutableList<Int>>()
dfs(root, 0, result)
return result
}
fun dfs(node: Node?, depth: Int, result: MutableList<MutableList<Int>>) {
if (node == null) return
if (depth >= result.size) {
result.add(mutableListOf())
}
result[depth].add(node.value)
dfs(node.left, depth + 1, result)
dfs(node.right, depth + 1, result)
}
getResult(tree)result = [[1], [2, 9], [1, 7, 5, 2], [9, 7]]
import java.lang.Integer.max
// Dynamic programming
// Задача. Дан массив из N целых чисел arr.
// Найдите длину максимальной возрастающей подпоследовательности в этом массиве.
// Например, при arr=[2, 3, 6, 4, 1, 3, 5, 4, 7] искомая подпоследовательность —
// [2, 3, 4, 5, 7] и поэтому ответ равен 5.
fun longestIncreasingSubsequence(list: List<Int>): Int {
val dp = MutableList(list.size) { 1 }
for (i in 1 until list.size) {
for (j in 0..i) {
if (list[j] < list[i]) {
dp[i] = max(dp[i], dp[j] + 1)
}
}
}
return dp.maxOf { it }
}
longestIncreasingSubsequence(listOf(2, 3, 6, 4, 1, 3, 5, 4, 7))Result = 5
Здесь вы найдёте ресурсы, посвящённые подготовке к алгоритмическим собеседованиям.
- InterviewBit — другой хороший ресурс с задачами для собеседований. Не нужно возиться с фильтрами, можно пройти по всем интересующим темам.
- Pramp — сервис для проведения peer-to-peer пробных интервью на английском языке. Вам назначается случайный человек в пару и вы собеседуете его, а потом он — вас. Очень полезно попробовать себя в роли не только собеседуемого, но и собеседущего — это поможет вам лучше почувствовать, как интервьюер оценивает кандидата. Очень рекомендуем в процессе подготовки к алгоритмическим интервью на английском языке.
- Как проходят алгоритмические секции на собеседованиях в Яндекс — статья про подготовку к собеседованиям от Яндекса.
- Вебинар «Открытое алгоритмическое собеседование». Можно посмотреть перед тем, как вы пойдёте на первое интервью, чтобы прочувствовать формат в динамике.
- Coding Interview University — самый полный гайд по подготовке к собеседованиям. Настолько полный, что на его прохождение потребуется очень много времени: автор говорит о 8–12 часов в день в течение 8 месяцев. Поэтому оставляем его тут на случай, если вы настроены максимально решительно.
import java.lang.Integer.max
// Дан массив целых чисел.
// Написать функцию, которая принимает этот массив и возвращает длину
// максимального неприрывного подмассива в котором нет повторяющихся элементов.
fun findSizeMaxIncreasedSequences(list: List<Int>): Int {
val hashMap = hashMapOf<Int,Int>()
var maxLength = 0
var currentLength = 0
var prevIndex = -1
list.forEachIndexed { index, number ->
val hashIndex = hashMap[number]
if (hashIndex != null && hashIndex > prevIndex) {
currentLength = index - hashIndex
prevIndex = hashIndex
} else {
++currentLength
}
hashMap[number] = index
maxLength = max(currentLength, maxLength)
}
return maxLength
}
findSizeMaxIncreasedSequences(listOf(1, 2, 3, 2, 4, 5, 6, 4, 7, 9, 8, 1, 5))
findSizeMaxIncreasedSequences(listOf(2, 2))// Дан массів целых чісел дліны N опісываюўій едініцу товара на протяженіі N дней
// Мы каждый день проізводім по одной едініце товара/ У нас есть склад где мы можем храніть товар
// Требуется вычісліть максімальную сумму которую мы можем выручіть за проізведенные товары
// с учетом того что к концу всего періода все товары проданы
// 1 3 1 2 -> 10
// 1, 2, 4, 5 ,1 ,2, 3, 3, 1 -> 33
fun findSum(list: List<Int>): Int {
var base = list.lastOrNull() ?: return 0
var allSum = base
for (ind in list.size - 2 downTo 0) {
if (base < list[ind]) {
base = list[ind]
}
allSum += base
}
return allSum
}
findSum(listOf(1, 2, 4, 5 ,1 ,2, 3, 3, 1))import java.lang.Integer.max
// (1, 7, 3, 15, 2, 5, 1, 4, 2, 2) -> 22 (15 + 7)
// 15, 7, 5, 4, 3, 2, 2, 1, 1 <-- sorted
fun maxWeight(list: List<Int>): Int {
return when {
list.isEmpty() -> 0
list.size == 1 -> list.first()
list.size == 2 -> list[0] + list[1]
else -> {
val sortedList = list.sortedDescending()
var max = 0
var curr = 0
var prev = 0
var left = 0
var right = 0
while (left < list.size - 1) {
right = left + 1
curr = sortedList[left] + sortedList[right]
prev = sortedList[left] - sortedList[right]
while (right < list.size - 2 && prev <= sortedList[right + 1]) {
right++
curr += sortedList[right]
prev = sortedList[left] - sortedList[right]
}
max = max(curr, max)
if (right >= list.size - 1) {
return max
} else {
left++
}
}
return max
}
}}
maxWeight(listOf(1, 7, 3, 15, 2, 5, 1, 4, 2, 2))result: 22
Назовем строку хорошей, если в ней нет двух соседних букв, которые различаются только регистром. Например, строка «abba» хорошая, а строка «aBba» нет.
Со строкой можно делать преобразование: если два соседних символа обозначают одну и ту же букву, но записаны в разных регистрах, то их можно удалить. При этом строка «схлопнется», то есть пробелов при удалении не образуется.
Цепочкой таких преобразований можно превратить любую строку в хорошую.
По заданной строке найдите хорошую строку, в которую ее можно превратить.
import java.util.ArrayDeque
fun isBad(c1: Char, c2: Char) = c1 != c2 && c1.lowercase() == c2.lowercase()
fun convertToGoodString(s: String): String {
return when {
s.length < 2 -> s
else -> {
val stack = ArrayDeque<Char>()
stack.push(s[0])
for (index in 1 until s.length) {
val prev = stack.peek()
if (isBad(prev, s[index])) {
stack.pop()
} else {
stack.push(s[index])
}
}
stack.reversed().joinToString("")
}
}
}
convertToGoodString("gaaBbAcCAgg") // "ggg"
convertToGoodString("vxOoOoVvx") // "vxx"
convertToGoodString("AbBa") // ""
convertToGoodString("w") // "w"// Даны две строки, требуется определить возможно ли не более чем
// за одну операцию по отношению к одной строке получить другую
// Операции:
// - удалить 1 символ
// - вставить 1 символ
// - заменить 1 символ на другой
// Операции можно совершать с любым символом исходной строки
// add -> s2 - s1 = 1
// delete -> s1 - s2 = 1
// replace -> s1 - s2 = 0
// difference - how much one str bigger than another
// diff - the absolute value of difference
// str1 - smallest string
// str2 - biggest string
// cur1 - index of str1
// cur2 - index of str2
fun isOneOpEdit(s1: String, s2: String): Boolean {
val difference = s1.length - s2.length
when {
s1 == s2 -> return false
difference !in -1..1 -> return false
else -> {
val (str1, str2, diff) = if (difference < 0) {
Triple(s1, s2, -difference)
} else {
Triple(s2, s1, difference)
}
var cur1 = 0
var cur2 = 0
var lev = 0
while (cur1 < str1.length) {
while (cur2 < str2.length && str1[cur1] != str2[cur2]) {
++lev
if (lev > 1) return false
cur2++
cur1 = cur1 + 1 - diff
}
cur1++
cur2++
}
return true
}
}
}
data class Test(
val s1: String,
val s2: String,
val expectedResult: Boolean
)
fun playTests() {
val tests = listOf(
Test("cat", "dog", false),
Test("cats", "acts", false),
Test("cats", "catsss", false),
Test("cats", "catdt", false),
Test("c", "c", false),
Test("cats", "cat", true),
Test("cat", "cut", true),
Test("cats", "casts", true),
Test("cata", "catb", true)
)
tests.forEach { test ->
val result = isOneOpEdit(test.s1, test.s2)
if (result == test.expectedResult) {
println(
"OK - For s1 = \"${test.s1}\" and s2 = \"${test.s2}\" " +
"result = ${test.expectedResult}"
)
} else {
println(
"WRONG - For s1 = \"${test.s1}\" and s2 = \"${test.s2}\" " +
"result = $result, but expected result = ${test.expectedResult}"
)
}
}
}