Наименьшее число, которое невозможно сформировать из суммы чисел из массива

Эта проблема была задана мне в интервью Amazon -

Учитывая массив положительных целых чисел, вам нужно найти наименьшее положительное целое число, которое невозможно сформировать из суммы чисел из массива.

Пример:

Array:[4 13 2 3 1]
result= 11 { Since 11 was smallest positive number which can not be formed from the given array elements }


Что я сделал:

  • отсортировано по массиву
  • вычислил сумму префикса
  • Измените массив сумм и проверьте, если следующий элемент меньше 1 больше суммы, т.е. A [j] <= (сумма + 1). Если не так, тогда ответ будет be sum + 1

Но это решение было nlog (n).

Интервьюер не был удовлетворен этим и попросил решение менее чем за время O (n log n).

Ответ 1

Рассмотрим все целые числа в интервале [2 i.. 2 я + 1 - 1]. И пусть все целые числа ниже 2 i могут быть сформированы из суммы чисел из заданного массива. Предположим также, что мы уже знаем C, что является суммой всех чисел ниже 2 i. Если C >= 2 я + 1 - 1, каждое число в этом интервале может быть представлено в виде суммы заданных чисел. В противном случае мы могли бы проверить, имеет ли интервал [2 i.. C + 1] любое число из заданного массива. И если такого числа нет, C + 1 - это то, что мы искали.

Вот эскиз алгоритма:

  • Для каждого номера входа определите, к какому интервалу он принадлежит, и обновите соответствующую сумму: S[int_log(x)] += x.
  • Вычислить префиксную сумму для массива S: foreach i: C[i] = C[i-1] + S[i].
  • Фильтровать массив C, чтобы сохранить только записи со значениями ниже, чем следующая мощность 2.
  • Еще раз сканируйте массив ввода и обратите внимание, какой из интервалов [2 i.. C + 1] содержит хотя бы один номер входа: i = int_log(x) - 1; B[i] |= (x <= C[i] + 1).
  • Найдите первый интервал, который не отфильтровывается на шаге №3, и соответствующий элемент B[] не установлен на шаге 4.

Если не очевидно, почему мы можем применить шаг 3, то это доказательство. Выберите любое число между 2 i и C, затем последовательно вычтите из него все числа ниже 2 i в порядке убывания. В конце концов мы получаем либо некоторое число, меньшее, чем последнее вычитаемое число или ноль. Если результат равен нулю, просто соедините все вычитаемые числа и у нас есть представление выбранного числа. Если результат отличен от нуля и меньше последнего вычитаемого числа, этот результат также меньше 2 i поэтому он является "представимым", и ни один из вычитаемых чисел не используется для его представления. Когда мы добавляем эти вычитаемые числа назад, мы имеем представление выбранного числа. Это также говорит о том, что вместо фильтрации интервалов один за другим мы могли бы пропустить несколько интервалов одновременно, перейдя непосредственно к int_log из C.

Сложность времени определяется функцией int_log(), которая представляет собой целочисленный логарифм или индекс самого старшего бита в числе. Если наш набор команд содержит целочисленный логарифм или любой его эквивалент (подсчитывает начальные нули или трюки с номерами с плавающей запятой), тогда сложность O (n). В противном случае мы могли бы использовать некоторый бит-хакинг для реализации int_log() в O (log log U) и получить временную сложность O (n * log log U). (Здесь U - наибольшее число в массиве).

Если шаг 1 (в дополнение к обновлению суммы) также обновит минимальное значение в заданном диапазоне, шаг 4 больше не нужен. Мы могли бы просто сравнить C [i] с Min [i + 1]. Это означает, что нам нужен только однопроходный входной массив. Или мы могли бы применить этот алгоритм не к массиву, а к потоку чисел.

Несколько примеров:

Input:       [ 4 13  2  3  1]    [ 1  2  3  9]    [ 1  1  2  9]
int_log:       2  3  1  1  0       0  1  1  3       0  0  1  3

int_log:     0  1  2  3          0  1  2  3       0  1  2  3
S:           1  5  4 13          1  5  0  9       2  2  0  9
C:           1  6 10 23          1  6  6 15       2  4  4 13
filtered(C): n  n  n  n          n  n  n  n       n  n  n  n
number in
[2^i..C+1]:  2  4  -             2  -  -          2  -  -
C+1:              11                7                5

Для многоточечных входных номеров для этого подхода требуется время O (n * log M) и O (log M). Где M - наибольшее число в массиве. То же самое время нужно просто, чтобы прочитать все числа (и в худшем случае нам нужен каждый их бит).

Тем не менее этот результат может быть улучшен до O (n * log R), где R - значение, найденное этим алгоритмом (на самом деле, его выходной вариант). Единственная модификация, необходимая для этой оптимизации, - это вместо того, чтобы обрабатывать целые числа сразу, обрабатывать их по цифрам: первый проход обрабатывает младшие бит каждого номера (например, бит 0..63), второй проход - следующие биты (например, 64..127) и т.д. После того, как результат найден, мы могли бы игнорировать все биты более высокого порядка. Также это уменьшает требования к пространству для чисел O (K), где K - количество бит в машинном слове.

Ответ 2

Там есть красивый алгоритм для решения этой проблемы во времени O (n + Sort), где Sort - это количество времени, необходимое для сортировки входного массива.

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

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

  • Если текущее число больше наименьшего числа, которое вы не можете сделать до сих пор, то вы знаете наименьшее количество, которое вы не можете сделать - это тот, который вы записали, и все готово.
  • В противном случае текущее число меньше наименьшего числа, которое вы не можете сделать. Утверждение состоит в том, что вы действительно можете сделать это число. Прямо сейчас вы знаете наименьшее число, которое вы не можете сделать с помощью первых k элементов массива (назовите его candidate) и теперь смотрите на значение A[k]. Поэтому число candidate - A[k] должно быть некоторым числом, которое вы действительно можете сделать с помощью первых k элементов массива, поскольку в противном случае candidate - A[k] будет меньшим числом, чем наименьшее число, которое вы, как утверждается, не можете сделать с помощью первых k чисел в массив. Кроме того, вы можете сделать любое число в диапазоне от candidate до candidate + A[k] включительно, так как вы можете начать с любого числа в диапазоне от 1 до A[k] включительно, а затем добавить candidate - 1 к нему. Поэтому установите candidate на candidate + A[k] и увеличивайте k.

В псевдокоде:

Sort(A)
candidate = 1
for i from 1 to length(A):
   if A[i] > candidate: return candidate
   else: candidate = candidate + A[i]
return candidate

Здесь тестовый прогон на [4, 13, 2, 1, 3]. Сортируйте массив, чтобы получить [1, 2, 3, 4, 13]. Затем установите candidate в 1. Затем сделаем следующее:

  • A [1] = 1, candidate= 1:
    • A [1] & le; candidate, поэтому установите candidate = candidate + A[1] = 2
  • A [2] = 2, candidate= 2:
    • A [2] & le; candidate, поэтому установите candidate = candidate + A[2] = 4
  • A [3] = 3, candidate= 4:
    • A [3] & le; candidate, поэтому установите candidate = candidate + A[3] = 7
  • A [4] = 4, candidate= 7:
    • A [4] & le; candidate, поэтому установите candidate = candidate + A[4] = 11
  • A [5] = 13, candidate= 11:
    • A [4] > candidate, поэтому верните candidate (11).

Итак, ответ 11.

Время выполнения - O (n + Сортировка), поскольку вне сортировки время выполнения O (n). Вы можете четко сортировать в O (n log n) время с помощью heapsort, и если вы знаете некоторую верхнюю границу чисел, которые вы можете сортировать по времени O (n log U) (где U - максимально возможное число), используя сортировку radix. Если U - фиксированная константа (например, 10 9), то сортировка радикса выполняется во времени O (n), и весь этот алгоритм также выполняется во времени O (n).

Надеюсь, это поможет!

Ответ 3

Используйте битвекторы для выполнения этого в линейном времени.

Начните с пустого битвектора b. Затем для каждого элемента k в вашем массиве выполните следующее:

b = b | b < k | 2 ^ (к-1)

Чтобы быть ясным, i-й элемент установлен в 1, чтобы представить число i, а | k устанавливает k-й элемент в 1.

После завершения обработки массива индекс первого нуля в b является вашим ответом (считая справа, начиная с 1).

  • Ь = 0
  • процесс 4: b = b | b < 4 | 1000 = 1000
  • процесс 13: b = b | b < 13 | 1000000000000 = 10001000000001000
  • процесс 2: b = b | b < 2 < 2 | 10 = 1010101000000101010
  • процесс 3: b = b | b < 3 | 100 = 1011111101000101111110
  • процесс 1: b = b | b < 1 < 1 | 1 = 11111111111001111111111

Первый ноль: позиция 11.