Как стать автором
Обновить
70.69
Слёрм
Учебный центр для тех, кто работает в IT

Лучшая задача по программированию для собеседования

Время на прочтение 7 мин
Количество просмотров 62K
Автор оригинала: Rohit Verma

Готовиться к собеседованию можно по‑разному: смотреть ролики на YouTube, читать документацию, положиться на судьбу и тд. В большинстве случаев кандидатам предложат решить одну или несколько задач. В этой статье вас ждет подробный разбор реальной задачки, рекомендации к ее решению и объяснение ожиданий интервьюера от кандидатов.

Найдите любое повторяющееся число

Дано:

массив из N+1 целых чисел, который содержит элементы в диапазоне [1, N].

Найти:

любое повторяющееся число.

Вопрос очевидный, но кандидаты часто задают уточняющие вопросы, и это хорошо.

Вот что они обычно спрашивают:

Кандидат: Можно пример?

Интервьюер: [4, 3, 1, 1, 2].

Кандидат: Только одно число повторяется?

Интервьюер: Нет, не обязательно: [4, 3, 3, 1, 1, 2].

Кандидат: А там точно будут повторяющиеся числа?

Это плохой уточняющий вопрос, ведь мы уже знаем, что размер массива N+1, а уникальных элементов в нем только N, так что по принципу Дирихле каким‑то кроликам придётся сидеть в одном домике.

Интервьюер: А разве может быть массив длиной N+1 с элементами в диапазоне [1, N], в котором числа не повторяются?

Кандидат понимает условия, начинает обдумывать решение и предполагает, что это довольно лёгкая задача.

Продолжаем диалог.

Кандидат: Если решать в лоб, то можно проверить, встречается ли это число ещё раз в массиве.

С этого начинает каждый кандидат. Я считаю, что главное начать с какого‑то более‑менее оптимального решения.

Интервьюер: А есть более эффективные решения? (Можно ещё намекнуть на временную сложность)

Кандидат: Можно использовать HashMap и считать каждый элемент массива. Как только попадётся элемент, который встречается больше одного раза, это и будет наш ответ.

О, уже лучше. Мы нашли решение O(N), но всё равно слишком тривиальное. В идеале кандидат должен с него и начать.

Интервьюер: Посчитайте временную и пространственную сложность этого решения.

Кандидат: Временная сложность O(N), пространственная сложность O(N).

Пока всё хорошо, спрашиваем дальше.

Интервьюер: Можно решить эту задачу, не используя лишнее пространство?

Кандидат: Можно отсортировать массив. Повторяющиеся элементы будут рядом, мы легко можем их найти.

Интервьюер: Тогда временная сложность вырастет до O(NLogN). Что ещё можно сделать?

На этом этапе кандидаты иногда думают, что это математическая задача и, например, пытаются посчитать сумму N*(N+1)/2 и т. д. В итоге запутываются и начинают решать другую задачу, в которой нужно найти недостающий и повторяющийся элемент, если в массиве только одно число повторяется дважды и элементы находятся в диапазоне [1, N], причём остальные элементы встречаются только один раз.

Сумма элементов в диапазоне нам никак не поможет, потому что мы не знаем, сколько элементов повторятся и сколько раз они повторяются. На этом этапе большинство кандидатов подвисает.

Подсказка: А если как с HashMap, только обозначать элементы индексами массива?

Кандидат: Аааааа, понятно! Если элементы находятся в диапазоне [1, N], а размер массива N+1, можно использовать индекс массива, чтобы помечать, присутствует элемент или нет. Берём число как индекс и меняем знак у числа по этому индексу на минус. Написать код? Или привести пример?

Интервьюер: Приведите пример.

Кандидат:
— Массив = [4, 1, 3, 2, 5, 4, 5, 5]
— Берём 4, переходим на 4-й индекс и меняем знак на минус
— Массив = [4, 1, 3, 2, -5, 4, 5, 5]
— Нужно взять абсолютное значение.
— Обрабатываем 1, массив = [4, -1, 3, 2, -5, 4, 5, 5]
— Обрабатываем 3, массив = [4, -1, 3, -2, -5, 4, 5, 5]
— Обрабатываем -2, массив = [4, -1, -3, -2, -5, 4, 5, 5]
— Обрабатываем -5, массив = [4, -1, -3, -2, -5, -4, 5, 5]
— Обрабатываем -4, массив = [4, -1, -3, -2, -5, -4, 5, 5], abs(-4) = 4, а число на четвёртом индексе уже отрицательное, значит число 4 встречается второй раз, это и есть наш ответ.

Интервьюер: Можете написать код?

Кандидат: Да.

Вот код:

int findAnyRepeatedNumber(int[] arr) {
	for(int i = 0; i < arr.length; i++) {
		int index = Math.abs(arr[i]);
		if(arr[index] < 0) return index;
		arr[index] = -arr[index];
	}
	throw new IllegalArgumentException("Invalid input array it should have elements in range [1, arr.length-1]");
}

Кандидату может показаться, что он решил задачу. Но интервьюер ещё не закончил.

Интервьюер: Что если массив неизменяемый?

Кандидат: Нельзя менять входящий массив?

Интервьюер: Да.

Кандидат: А если мы изменим массив, а потом отменим изменения? Так можно?

Интервьюер: Нет. Сам по себе массив неизменяемый и нельзя использовать дополнительное пространство.

На этом этапе большинство кандидатов сдаётся. Но некоторые задают дополнительные вопросы.

Кандидат: Нам нужно решение O(N)?

Интервьюер: Сейчас у нас есть неизменяемый массив и нужно решение лучше O(N²). Как решить задачу с временной сложностью O(NLogN)?

Кандидат: Это задачка на поиск. Сложность LogN бывает у бинарного поиска или алгоритма «разделяй и властвуй». Но массив‑то не отсортирован.

Подсказка: А если всё‑таки применить бинарный поиск? Массив не отсортирован, но пространство поиска [1, N] отсортировано. Если сложность должна быть O(NLogN), можно выполнять операции O(N) с массивом LogN раз.

Эту подсказку кандидаты понимают редко.

Кандидат: Понимаю. Пожалуй, можно выполнить бинарный поиск повторяющегося числа. Например, можно пройтись по списку и посчитать число целых чисел от 1 до N/2. Если количество получилось больше, чем чисел в этом диапазоне, мы поймём, что повторяющееся число находится в этом диапазоне. В противном случае повторяется число из диапазона от N/2+1 до N. Когда мы поймём, в какой половине диапазона повторяющееся число, можно снова разделить диапазон пополам, и так пока не найдём само число. Временная сложность получается O(NLogN), а пространственная — O(1).

Реализация:

int findAnyRepeatedNumber(int[] arr) {
    // Range [1, N]      
    int low = 1, high = arr.length - 1;
    int duplicate = -1;
    while (low <= high) {
        int cur = (low + high) / 2;
        int count = countLessThatOrEqual(cur, arr);
        if (count > cur) {
            duplicate = cur;
            high = cur - 1;
        } else {
            low = cur + 1;
        }
    }
    if(duplicate == -1) {
      throw new IllegalArgumentException("Invalid input array it should have elements in range [1, arr.length-1]");
    }
    return duplicate;
}

int countLessThatOrEqual(int num, int[] arr) {
    int count = 0;
    for (int x : arr) {
        if (x <= num)
            count++;
    }
    return count;
}

Временная сложность O(NLogN), пространственная сложность O(1)

Интервьюер: Отлично!

Интервьюер: Ещё один вопрос.

Кандидат: (про себя: Ну что ещё?!) Да?

Интервьюер: Если повторяется только одно число, можно решить задачу с временной сложностью O(N)?

Кандидат: (про себя: Вряд ли.) Дайте подумать.

На этом этапе большинство кандидатов отчаивается. А ведь поначалу задачка показалась им лёгкой! Почти всем нужна подсказка.

Курс «Алгоритмы: roadmap для работы и собеседований»

Подсказка: Про зайца и черепаху слышали?

Кандидат: Да, это алгоритм, который определяет цикл в связном списке и помогает найти начальную точку цикла.

Интервьюер: Так. В нашей задачке тоже можно увидеть цикл.

Кандидат: Где?

Интервьюер: Давайте представим, что наш массив — это связный список, в котором число по каждому индексу указывает на индекс, равный этому числу.

Кандидат: Можно пример?

Возьмём массив [2, 6, 4, 1, 3, 1, 5].

Нулевой индекс указывает на второй, второй указывает на четвёртый и так далее.

Кандидат: Понятно.

Интервьюер: Можете написать код?

Кандидат: Вот код.

int findAnyRepeatedNumber(int[] arr) {
    int slow = arr[0];
    int fast = arr[0];
    do {
        slow = arr[slow];
        fast = arr[arr[fast]];
    } while(slow != fast);
    
    slow = arr[0];
    while (slow != fast) {
        slow = arr[slow];
        fast = arr[fast];
    }
    return fast;
}

Временная сложность O(N), пространственная сложность O(1), массив неизменяемый.

Это решение не сработает, если повторяться может несколько чисел.

Интервьюер: Ещё один вопрос.

Кандидат: (злится) С меня хватит.

Интервьюер: Да я же просто хотел спросить... когда вы сможете приступить к работе?

Это умный вопрос, потому что:

  • Кандидат разбирается в алгоритмах, пространственной и временной сложности и т. д.

  • Кандидат адаптируется к разным ограничениям и находит компромисс. Инженерам постоянно приходится работать в таких условиях, так что это важный навык.

  • Кандидат проявил креативность. Каждое ограничение заставляет начинать с нуля и придумывать совершенно новый подход.

  • Кандидат правильно смотрит на сложность. Некоторые кандидаты застревают, пытаясь найти решение с временной сложностью O(NlogN) и пространственной сложностью O(1). Им кажется, что использовать хеш или сравнивать все элементы списка с остальными элементами было бы слишком просто, поэтому они не предлагают эти банальные решения — но и более сложные придумать не могут. Это указывает на то, что кандидат склонен чрезмерно всё усложнять. Обычно я прошу следующих интервьюеров обращать внимание на эту склонность.

  • Кандидат готов учиться и решать сложные задачи. Некоторые люди оживляются, когда условия задачи становятся сложнее, а другие опускают руки или ленятся прилагать усилия.

Как решать задачи, которые не могут решить другие программисты

Подготовка к собеседованию и само собеседование – то еще испытание для тех, кто только делает первые шаги в IT. В текущих реалиях не всегда достаточно знать теорию и уметь кодить. Чтобы получить долгожданный оффер и выделиться среди других соискателей, вам понадобится знание алгоритмов. В этом вам поможет наш экспресс-курс «Алгоритмы: roadmap для работы и собеседований».

Вы научитесь

👉 решать задачи, которые не могут решить другие программисты

👉 узнаете, как знание алгоритмов и структур данных помогает устроиться в топовые компании FAANG: Apple, Amazon, Netflix, Google

Эти навыки помогут вам участвовать в сложных высокооплачиваемых проектах, а также успешно подготовиться к алгоритмическим задачам на собеседованиях в топовые компании.

В экспресс-курсе нет детального разбора существующих алгоритмов — здесь то, что используется на практике чаще всего. Знание алгоритмов и структур данных — не достаточное, но необходимое условие успешного прохождения собеседований.

Курс «Алгоритмы: roadmap для работы и собеседований» доступен в любое время. Вас ждут ёмкие лекции, практические задачи, приближенные к реальным, полезная литература по теме и доступ к материалам курса на 2 года. 

Сомневаетесь? Посмотрите первые две темы экспресс-курса бесплатно

Теги:
Хабы:
+22
Комментарии 271
Комментарии Комментарии 271

Публикации

Информация

Сайт
slurm.io
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия
Представитель
Антон Скобин