Некоторое время назад у меня был интересный опыт собеседования. Вопрос начался очень просто:
Q1 : У нас есть пакет , содержащий номера
1
,2
,3
...,100
. Каждый номер появляется ровно один раз, поэтому есть 100 номеров. Теперь один номер случайно выбирается из сумки. Найдите пропущенный номер.
Конечно, я слышал этот вопрос раньше, поэтому очень быстро ответил:
A1 : Ну, сумма чисел
1 + 2 + 3 + … + N
равна(N+1)(N/2)
(см. Википедия: сумма арифметических рядов ). ИбоN = 100
сумма есть5050
.Таким образом, если в сумке присутствуют все числа, сумма будет точно
5050
. Так как отсутствует одно число, сумма будет меньше этой, а разница в этом числе. Таким образом, мы можем найти это недостающее число воO(N)
времени иO(1)
пространстве.
В этот момент я думал, что у меня все хорошо, но внезапно вопрос принял неожиданный оборот:
Q2 : Это правильно, но как бы вы это сделали, если ДВЕ номера не хватает?
Я никогда раньше не видел / не слышал / не рассматривал эту вариацию, поэтому запаниковал и не смог ответить на вопрос. Интервьюер настаивал на том, чтобы знать мой мыслительный процесс, поэтому я упомянул, что, возможно, мы можем получить больше информации, сравнивая с ожидаемым продуктом, или, возможно, сделав второй проход после сбора некоторой информации с первого прохода и т. Д., Но я действительно просто снимал в темноте, а не на самом деле иметь четкий путь к решению.
Интервьюер попытался меня ободрить, сказав, что наличие второго уравнения действительно является одним из способов решения проблемы. В этот момент я был немного расстроен (из-за того, что не знал ответа заранее) и спросил, является ли это общей (читай: «полезной») техникой программирования или это просто ответ с подвохом.
Ответ интервьюера удивил меня: вы можете обобщить технику, чтобы найти 3 пропущенных числа. Фактически, вы можете обобщить это, чтобы найти k пропущенных чисел.
Qk : Если в сумке нет ровно k чисел, как бы вы нашли это эффективно?
Это было несколько месяцев назад, и я до сих пор не могу понять, что это за техника. Очевидно , что это Ω(N)
время нижней границы , так как мы должны сканировать все номера , по крайней мере , один раз, но интервьюер настаивает , что ВРЕМЯ и ПРОСТРАНСТВО сложность методы решения задачи (минус O(N)
входном сканирование времени) определяются в к не N .
Так что вопрос здесь прост:
- Как бы вы решили Q2 ?
- Как бы вы решили Q3 ?
- Как бы вы решили Qk ?
Разъяснения
- Обычно есть N чисел от 1 .. N , а не только 1..100.
- Я не ищу очевидное основанное на множестве решение, например, используя набор битов , кодируя присутствие / отсутствие каждого числа значением назначенного бита, поэтому используя
O(N)
биты в дополнительном пространстве. Мы не можем позволить себе любое дополнительное пространство , пропорциональное N . - Я также не ищу очевидного подхода первого порядка. Этот и основанный на множестве подходы заслуживают упоминания в интервью (они просты в реализации и в зависимости от N могут быть очень практичными). Я ищу решение Святого Грааля (которое может или не может быть практичным для реализации, но, тем не менее, имеет желаемые асимптотические характеристики).
Итак, еще раз, конечно, вы должны сканировать входные данные O(N)
, но вы можете захватить только небольшое количество информации (определяемой в терминах k, а не N ), а затем должны как-то найти k пропущенных чисел.
XOR
всех чисел от1
доn
, а затем сохранение результата со всеми числами в данном массиве. В конце концов, у вас есть пропущенный номер. В этом решении вам не нужно заботиться о переполнении, как при суммировании.Ответы:
Вот краткое изложение ссылки Димитриса Андреу .
Запомните сумму i-й степени, где i = 1,2, .., k. Это сводит проблему к решению системы уравнений
a 1 + a 2 + ... + a k = b 1
a 1 2 + a 2 2 + ... + a k 2 = b 2
...
a 1 k + a 2 k + ... + a k k = b k
Используя тождества Ньютона , зная, что b i позволяет вычислить
c 1 = a 1 + a 2 + ... a k
c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k
...
с к = а 1 а 2 ... a k
Если развернуть многочлен (xa 1 ) ... (xa k ), коэффициенты будут точно c 1 , ..., c k - см . Формулы Виета . Поскольку каждый полиномиальный фактор однозначно (кольцо полиномов является евклидовой областью ), это означает, что a i однозначно определены, с точностью до перестановки.
Это завершает доказательство того, что запоминание способностей достаточно для восстановления чисел. Для константы k это хороший подход.
Однако, когда k изменяется, прямой подход к вычислению c 1 , ..., c k является чрезмерно дорогим, поскольку, например, c k является произведением всех пропущенных чисел, величина n! / (Nk) !. Чтобы преодолеть это, выполните вычисления в поле Z q , где q такое простое число, что n <= q <2n - оно существует по постулату Бертрана . Доказательство менять не нужно, поскольку формулы все еще выполняются, а факторизация полиномов по-прежнему уникальна. Вам также нужен алгоритм для факторизации по конечным полям, например алгоритм Берлекампа или Кантора-Цассенхауза .
Псевдокод высокого уровня для константы k:
Для варьирования k найдите простое число n <= q <2n, используя, например, Миллера-Рабина, и выполните шаги с уменьшением всех чисел по модулю q.
РЕДАКТИРОВАТЬ: предыдущая версия этого ответа заявил, что вместо Z q , где q является простым, можно использовать конечное поле характеристики 2 (q = 2 ^ (log n)). Это не так, поскольку формулы Ньютона требуют деления на числа до k.
источник
q = 2^(log n)
. (Как ты сделал супер- и подписки ?!)O(N^2)
решение, возможно, превзошло бы эту красоту даже на достаточно высоком уровнеN
. Заставляет меня думать об этом: tinyurl.com/c8fwgw Тем не менее, отличная работа! У меня не хватило бы терпенияhash set
и итерация по1...N
набору с использованием поиска, чтобы определить, отсутствуют ли числа, будет самым общим, самым быстрым в среднем поk
вариациям, наиболее отлаживаемым, наиболее приемлемым и понятным решением. Конечно, математика впечатляет, но где-то по пути вам нужно быть инженером, а не математиком. Особенно, когда дело касается бизнеса.Вы найдете это, прочитав пару страниц алгоритмов Muthukrishnan - Data Stream: Головоломка 1: Поиск пропущенных чисел . Он показывает именно то обобщение, которое вы ищете . Возможно, именно это прочитал ваш интервьюер и почему он задал эти вопросы.
Теперь, если бы только люди начали удалять ответы, которые включены или заменены обработкой Мутукришнана, и облегчить поиск этого текста. :)
Также смотрите прямой ответ sdcvvc , который также включает псевдокод (ура! Нет необходимости читать эти хитрые математические формулировки :)) (спасибо, отличная работа!).
источник
Мы можем решить Q2, суммируя как сами числа, так и квадраты чисел.
Затем мы можем уменьшить проблему до
Где
x
иy
как далеко суммы ниже ожидаемых значений.Подставляя дает нам:
Что мы можем затем решить, чтобы определить наши пропущенные числа.
источник
Как указал @j_random_hacker, это очень похоже на поиск дубликатов в O (n) времени и O (1) пространстве , и адаптация моего ответа там тоже работает.
Предполагая, что «мешок» представлен массивом
A[]
размером 1N - k
, мы можем решить Qk воO(N)
времени иO(k)
дополнительном пространстве.Во- первых, мы расширим наш массив с
A[]
помощьюk
элементов, так что теперь размерN
. ЭтоO(k)
дополнительное пространство. Затем мы запускаем следующий алгоритм псевдокода:Первый цикл инициализирует
k
дополнительные записи так же, как и первая запись в массиве (это просто удобное значение, которое, как мы знаем, уже присутствует в массиве - после этого шага любые записи, отсутствующие в исходном массиве размераN-k
, все еще отсутствует в расширенном массиве).Второй цикл переставляет расширенный массив, так что если элемент
x
присутствует хотя бы один раз, то одна из этих записей будет в позицииA[x]
.Обратите внимание, что, хотя у него есть вложенный цикл, он все еще выполняется во
O(N)
времени - своп происходит только в том случае, если естьi
такойA[i] != i
, и каждый своп устанавливает как минимум один элемент, такойA[i] == i
, где это не было ранее. Это означает, что общее количество перестановок (и, следовательно, общее количество выполненийwhile
тела цикла) не болееN-1
.Третий цикл печатает те индексы массива
i
, которые не заняты значениемi
- это означает, что,i
должно быть, отсутствовал.источник
A[i]
, что означает, что следующая итерация не будет сравнивать те же два значения, что и предыдущая. НовоеA[i]
будет таким же, как последний циклA[A[i]]
, но новоеA[A[i]]
будет новым значением. Попробуйте и посмотрите.Я попросил 4-летнего ребенка решить эту проблему. Он отсортировал числа и затем сосчитал. Это требует пространства O (пол на кухне) и работает так же просто, как и многие шары.
источник
Не уверен, что это наиболее эффективное решение, но я бы перебрал все записи и использовал набор битов, чтобы запомнить, какие числа установлены, а затем проверил наличие 0 битов.
Мне нравятся простые решения - и я даже считаю, что это может быть быстрее, чем вычисление суммы или суммы квадратов и т. Д.
источник
O(N)
подсчет сортировка , ниO(N log N)
сравнение рода не то , что я ищу, хотя они оба очень простых решений.Я не проверял математику, но подозреваю, что вычисления
Σ(n^2)
на одном и том же проходе, когда мы вычисляемΣ(n)
, предоставят достаточно информации для получения двух пропущенных чисел,Σ(n^3)
а также, если их три, и так далее.источник
Проблема с решениями, основанными на суммах чисел, состоит в том, что они не учитывают стоимость хранения и работы с числами с большими показателями ... на практике, для того, чтобы он работал для очень больших n, использовалась бы библиотека больших чисел , Мы можем проанализировать использование пространства для этих алгоритмов.
Мы можем проанализировать временную и пространственную сложность алгоритмов sdcvvc и Dimitris Andreou.
Место хранения:
Так
l_j \in \Theta(j log n)
Общее количество использованного хранилища:
\sum_{j=1}^k l_j \in \Theta(k^2 log n)
Используемое пространство: при условии, что вычисления
a^j
занимаютceil(log_2 j)
время, общее время:Общее использованное время:
\Theta(kn log n)
Если это время и пространство удовлетворительное, вы можете использовать простой рекурсивный алгоритм. Пусть b! I будет i-й записью в сумке, n числом чисел перед удалением и k количеством изъятий. В синтаксисе Haskell ...
Используемое хранилище:
O(k)
для списка,O(log(n))
для стека:O(k + log(n))
этот алгоритм более интуитивен, имеет ту же сложность времени и использует меньше места.источник
isInRange
это O (log n) , а не O (1) : он сравнивает числа в диапазоне 1..n, поэтому он должен сравнивать O (log n) бит. Я не знаю, в какой степени эта ошибка влияет на остальную часть анализа.Подожди минуту. Поскольку вопрос установлен, в сумке есть 100 чисел. Независимо от того, насколько велико k, проблему можно решить за постоянное время, потому что вы можете использовать набор и удалять числа из набора в самое большее 100 - k итераций цикла. 100 постоянна. Набор оставшихся чисел - ваш ответ.
Если мы обобщим решение для чисел от 1 до N, ничего не изменится, за исключением того, что N не является константой, поэтому мы находимся в O (N - k) = O (N) времени. Например, если мы используем набор битов, мы устанавливаем биты в 1 за O (N), перебираем числа, устанавливая биты в 0 по мере продвижения (O (Nk) = O (N)), а затем есть ответ.
Мне кажется, что интервьюер спрашивал вас, как распечатать содержимое окончательного набора за O (k), а не O (N). Ясно, что с установленным битом вам нужно пройти через все N битов, чтобы определить, следует ли вам печатать число или нет. Однако, если вы измените способ реализации набора, вы можете распечатать числа за k итераций. Это делается путем помещения чисел в объект, который будет храниться как в хэш-наборе, так и в двусвязном списке. Когда вы удаляете объект из хеш-набора, вы также удаляете его из списка. Ответы останутся в списке, который теперь имеет длину k.
источник
Чтобы решить вопрос о 2 (и 3) пропущенных числах, вы можете изменить его
quickselect
, который в среднем работаетO(n)
и использует постоянную память, если разбиение выполняется на месте.Разбейте набор относительно случайного центра
p
на разделыl
, которые содержат числа, меньшие, чем центр, иr
которые содержат числа, большие, чем центр.Определите, в каких разделах находятся 2 пропущенных числа, сравнив значение сводки с размером каждого раздела (
p - 1 - count(l) = count of missing numbers in l
иn - count(r) - p = count of missing numbers in r
)a) Если в каждом разделе пропущено одно число, используйте подход разностей сумм, чтобы найти каждое пропущенное число.
(1 + 2 + ... + (p-1)) - sum(l) = missing #1
а также((p+1) + (p+2) ... + n) - sum(r) = missing #2
б) если один раздел отсутствует как число и раздел пуст, то недостающие цифры либо
(p-1,p-2)
или в(p+1,p+2)
зависимости от того, какой из разделов отсутствуют число.Если в одном разделе пропущены 2 числа, но он не пуст, вернитесь к этому разделу.
При наличии только 2 пропущенных чисел этот алгоритм всегда отбрасывает хотя бы один раздел, поэтому он сохраняет
O(n)
среднюю сложность быстрого выбора. Точно так же с 3 пропущенными числами этот алгоритм также отбрасывает по крайней мере один раздел с каждым проходом (потому что как с 2 пропущенными числами, самое большее только 1 раздел будет содержать несколько пропущенных чисел). Тем не менее, я не уверен, насколько снижается производительность, когда добавляется больше пропущенных чисел.Вот реализация, которая не использует разделение на месте, поэтому этот пример не соответствует требованию к пространству, но он иллюстрирует шаги алгоритма:
демонстрация
источник
Вот решение, которое использует k бит дополнительной памяти, без каких-либо хитрых уловок и просто. Время выполнения O (n), дополнительное пространство O (k). Просто чтобы доказать, что это можно решить, не читая сначала о решении и не будучи гением:
источник
(data [n - 1 - odd] % 2 == 1) ++odd;
?Можете ли вы проверить, существует ли каждый номер? Если да, вы можете попробовать это:
если пропущенные числа,
x
аy
затем:Итак, вы проверяете диапазон от
1
доmax(x)
и находите числоисточник
max(x)
значит, когдаx
число?Может быть, этот алгоритм может работать на вопрос 1:
Или даже лучше:
Этот алгоритм может быть фактически расширен для двух пропущенных чисел. Первый шаг остается прежним. Когда мы вызываем GetValue с двумя пропущенными числами, результатом будет
a1^a2
два пропущенных числа. Допустимval = a1^a2
Теперь, чтобы отсеять a1 и a2 от val, мы берем любой установленный бит в val. Допустим,
ith
бит установлен в val. Это означает, что a1 и a2 имеют разную четность вith
позиции бита. Теперь мы делаем еще одну итерацию для исходного массива и сохраняем два значения xor. Один для чисел, у которых установлен i-й бит, а другой - без установленного i-го бита. Теперь у нас есть две группы чисел, и она гарантированноa1 and a2
будет лежать в разных группах. Теперь повторите то же самое, что мы сделали для нахождения одного пропущенного элемента в каждом ведре.источник
k=1
, верно? Но мне нравится использоватьxor
сверх суммы, кажется, немного быстрее.Вы можете решить Q2, если у вас есть сумма обоих списков и произведение обоих списков.
(l1 - оригинал, l2 - модифицированный список)
Мы можем оптимизировать это, поскольку сумма арифметического ряда в n раз больше среднего первого и последнего членов:
Теперь мы знаем, что (если a и b - удаленные числа):
Таким образом, мы можем изменить:
И умножить:
И переставить так, чтобы правая сторона была равна нулю
Тогда мы можем решить с помощью квадратной формулы:
Пример кода Python 3:
Я не знаю сложности функций sqrt, Reduce и Sum, поэтому я не могу решить сложность этого решения (если кто-то знает, пожалуйста, прокомментируйте ниже.)
источник
x1*x2*x3*...
?Для Q2 это решение, которое немного более неэффективно, чем другие, но все еще имеет время выполнения O (N) и занимает пространство O (k).
Идея состоит в том, чтобы запустить оригинальный алгоритм два раза. В первом вы получите общее число, которое отсутствует, что дает вам верхнюю границу пропущенных чисел. Давайте назовем этот номер
N
. Вы знаете, что пропущенные два числа будут суммироваться доN
, поэтому первое число может быть только в интервале,[1, floor((N-1)/2)]
пока второе будет[floor(N/2)+1,N-1]
.Таким образом, вы снова зацикливаетесь на всех числах, отбрасывая все числа, которые не включены в первый интервал. Те, которые, вы отслеживаете их сумму. Наконец, вы узнаете одно из двух пропущенных чисел и, в дополнение, второе.
У меня есть ощущение, что этот метод может быть обобщен, и, возможно, несколько запросов выполняются "параллельно" в течение одного прохода по входу, но я еще не выяснил, как.
источник
Я думаю, что это можно сделать без каких-либо сложных математических уравнений и теорий. Ниже приведено предложение по решению проблемы сложности на месте и времени O (2n):
Предположения формы ввода:
Количество чисел в сумке = n
Количество пропущенных номеров = k
Числа в сумке представлены массивом длины n
Длина входного массива для алгоритма = n
Недостающие записи в массиве (числа, извлеченные из пакета) заменяются значением первого элемента в массиве.
Например. Изначально сумка выглядит как [2,9,3,7,8,6,4,5,1,10]. Если выбрано 4, значение 4 станет 2 (первый элемент массива). Таким образом, после того, как вынуть 4, сумка будет выглядеть как [2,9,3,7,8,6,2,5,1,10]
Ключ к этому решению состоит в том, чтобы пометить INDEX посещенного номера, отрицая значение в этом INDEX, когда массив пересекается.
источник
Существует общий способ обобщения потоковых алгоритмов, подобных этому. Идея состоит в том, чтобы использовать немного рандомизации, чтобы, как мы надеемся, «распределить»
k
элементы по независимым подзадачам, где наш оригинальный алгоритм решает эту проблему для нас. Этот метод используется, среди прочего, при восстановлении разреженных сигналов.a
размеромu = k^2
.h : {1,...,n} -> {1,...,u}
. (Как многократная смена )i
в1, ..., n
увеличенииa[h(i)] += i
x
во входном потоке уменьшатьa[h(x)] -= x
.Если все пропущенные числа были хэшированы в разные сегменты, ненулевые элементы массива теперь будут содержать пропущенные числа.
Вероятность того, что конкретная пара отправляется в одно и то же ведро, меньше, чем
1/u
по определению универсальной хеш-функции. Поскольку речьk^2/2
идет о парах, мы имеем вероятность ошибки не болееk^2/2/u=1/2
. То есть мы добиваемся успеха с вероятностью не менее 50%, и если мы увеличиваем,u
мы увеличиваем наши шансы.Обратите внимание, что этот алгоритм занимает
k^2 logn
биты пространства (нам нужноlogn
битов на массив). Это соответствует пространству, требуемому для ответа @Dimitris Andreou (В частности, требование к пространству для полиномиальной факторизации, которая также бывает рандомизированной.) Этот алгоритм также имеет константу время на обновление, а не времяk
в случае сумм мощности.Фактически, мы можем быть даже более эффективными, чем метод суммирования мощности, используя прием, описанный в комментариях.
источник
xor
в каждом контейнере, а неsum
, если это быстрее на нашей машине.k <= sqrt(n)
- по крайней мере, еслиu=k^2
? Предположим, что k = 11 и n = 100, тогда у вас будет 121 сегмент, и алгоритм в конечном итоге будет похож на массив из 100 битов, который вы проверяете при чтении каждого # из потока. Увеличениеu
увеличивает шансы на успех, но есть предел того, насколько вы можете увеличить его, прежде чем вы превысите ограничение пространства.n
гораздо большего, чемk
, я думаю, но на самом деле вы можете сэкономить местоk logn
с помощью метода, очень похожего на описанный хеширование, при этом сохраняя постоянные обновления времени. Он описан в gnunet.org/eppstein-set-reconciliation , как и метод суммы мощностей, но в основном вы хэшируете «два из k» сегментов с помощью сильной хэш-функции, такой как табулирование, которое гарантирует, что в некоторых сегментах будет только один элемент , Для декодирования вы идентифицируете этотОчень простое решение Q2, на которое я удивляюсь, никто уже не ответил. Используйте метод из Q1, чтобы найти сумму двух пропущенных чисел. Обозначим его через S, тогда одно из пропущенных чисел меньше, чем S / 2, а другое больше, чем S / 2 (дух). Суммируйте все числа от 1 до S / 2 и сравните его с результатом формулы (аналогично методу в Q1), чтобы найти меньшее из пропущенных чисел. Вычтите это из S, чтобы найти большее пропущенное число.
источник
Очень хорошая проблема. Я бы пошел за использование разницы набора для Qk. Многие языки программирования даже поддерживают его, как в Ruby:
Возможно, это не самое эффективное решение, но я бы использовал его в реальной жизни, если бы столкнулся с такой задачей в этом случае (известные границы, низкие границы). Если бы набор чисел был очень большим, я бы, конечно, рассмотрел более эффективный алгоритм, но до этого мне было бы достаточно простого решения.
источник
Вы можете попробовать использовать фильтр Блума . Вставьте каждый номер в сумке в цвету, затем итерируйте полный набор 1-k, пока не сообщите о каждом не найденном. Это может не найти ответ во всех сценариях, но может быть достаточно хорошим решением.
источник
Я бы использовал другой подход к этому вопросу и исследовал интервьюера для получения более подробной информации о более крупной проблеме, которую он пытается решить. В зависимости от проблемы и требований, окружающих ее, очевидное решение на основе множеств может быть правильным, а подход «генерировать список и выбирать через него» может и не быть.
Например, может случиться так, что интервьюер собирается отправлять
n
сообщения и ему нужно знать,k
что не привело к ответу, и ему нужно знать это за минимальное время после настенного времениn-k
ответа. Давайте также скажем, что характер канала сообщений таков, что даже при работе на полную длину у вас есть достаточно времени для некоторой обработки между сообщениями без какого-либо влияния на время, необходимое для получения конечного результата после получения последнего ответа. Это время можно использовать для вставки некоторого идентифицирующего аспекта каждого отправленного сообщения в набор и удаления его по мере поступления каждого соответствующего ответа. Как только последний ответ получен, единственное, что нужно сделать, это удалить его идентификатор из набора, который в типичных реализациях принимаетO(log k+1)
, После этого набор содержит списокk
отсутствующих элементов, и дополнительная обработка не требуется.Это, конечно, не самый быстрый подход для пакетной обработки предварительно сгенерированных пакетов чисел, потому что все работает
O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))
. Но он работает для любого значенияk
(даже если оно не известно заранее) и в приведенном выше примере он был применен таким образом, чтобы минимизировать самый критический интервал.источник
Вы можете мотивировать решение, думая об этом с точки зрения симметрии (группы, на языке математики). Независимо от порядка набора чисел, ответ должен быть одинаковым. Если вы собираетесь использовать
k
функции для определения отсутствующих элементов, вам следует подумать о том, какие функции имеют это свойство: симметричное. Функцияs_1(x) = x_1 + x_2 + ... + x_n
является примером симметричной функции, но есть и другие, более высокой степени. В частности, рассмотрим элементарные симметричные функции . Элементарная симметричная функция степени 2 являетсяs_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n
суммой всех произведений двух элементов. Аналогично для элементарных симметрических функций степени 3 и выше. Они явно симметричны. Кроме того, оказывается, что они являются строительными блоками для всех симметричных функций.Вы можете построить элементарные симметричные функции по ходу, отметив это
s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))
. Дальнейшая мысль должна убедить вас в этомs_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))
и так далее, чтобы их можно было вычислить за один проход.Как нам определить, какие элементы отсутствовали в массиве? Подумайте о полиноме
(z-x_1)(z-x_2)...(z-x_n)
. Он оценивает,0
если вы введете любое из чиселx_i
. Расширяя полином, вы получаетеz^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n
. Здесь также появляются элементарные симметричные функции, что неудивительно, поскольку многочлен должен оставаться прежним, если мы применим любую перестановку к корням.Таким образом, мы можем построить многочлен и попытаться разложить его, чтобы выяснить, какие числа не входят в набор, как уже упоминали другие.
Наконец, если нас беспокоит переполнение памяти большими числами (n-й симметричный многочлен будет порядка
100!
), мы можем выполнить эти вычисления,mod p
гдеp
простое число больше 100. В этом случае мы оцениваем многочленmod p
и находим, что оно снова вычисляет чтобы ,0
когда вход представляет собой число в наборе, и она вычисляется в ненулевое значение , когда вход числа , не в наборе. Однако, как уже отмечалось, для получения значений из полинома во времени , которое зависит отk
, неN
, мы должны фактор многочленmod p
.источник
Еще одним способом является использование остаточной фильтрации графов.
Предположим, у нас есть номера от 1 до 4, а 3 отсутствует. Бинарное представление следующее,
1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b
И я могу создать потоковую диаграмму, как показано ниже.
Обратите внимание, что потоковый граф содержит х узлов, а х - количество битов. А максимальное количество ребер равно (2 * х) -2.
Таким образом, для 32-разрядного целого числа потребуется O (32) пробел или O (1) пробел.
Теперь, если я уберу емкость для каждого числа, начиная с 1,2,4, то у меня останется остаточный график.
Наконец, я запустите цикл, как показано ниже,
Теперь результат
result
содержит числа, которые также не пропущены (ложное срабатывание). Но k <= (размер результата) <= n, когдаk
отсутствуют элементы.Я пройду по данному списку в последний раз, чтобы отметить пропущенный результат или нет.
Таким образом, сложность времени будет O (n).
Наконец, можно уменьшить число ложноположительных (и пространство , необходимое), принимая узлы
00
,01
,11
,10
вместо того , чтобы просто0
и1
.источник
Вам, вероятно, нужно уточнить, что означает O (k).
Вот тривиальное решение для произвольного k: для каждого v в вашем наборе чисел накапливайте сумму 2 ^ v. В конце, цикл i от 1 до N. Если сумма поразрядно ANDed с 2 ^ i равна нулю, то i отсутствует. (Или численно, если пол суммы, деленной на 2 ^ i четен. Или
sum modulo 2^(i+1)) < 2^i
.)Легко, правда? O (N) время, O (1) память, и это поддерживает произвольное k.
За исключением того, что вы вычисляете огромные числа, которые на реальном компьютере требуют пространства O (N). Фактически это решение идентично битовому вектору.
Таким образом, вы можете быть умным и вычислить сумму и сумму квадратов и сумму кубов ... вплоть до суммы v ^ k, и сделать причудливую математику, чтобы извлечь результат. Но это тоже большие цифры, поэтому возникает вопрос: о какой абстрактной модели работы идет речь? Сколько места в O (1) пространстве, и сколько времени нужно, чтобы сложить числа любого размера вам нужно?
источник
Вот решение, которое не полагается на сложную математику, как это делают ответы sdcvvc / Dimitris Andreou, не меняет входной массив, как это сделали caf и полковник Panic, и не использует набор огромных размеров, как Chris Lercher, JeremyP и многие другие сделали. По сути, я начал с идеи Свалорцена / Гилада Дойча для Q2, обобщил ее для общего случая Qk и реализовал в Java, чтобы доказать, что алгоритм работает.
Идея
Предположим, у нас есть произвольный интервал I, из которого мы знаем только то, что он содержит хотя бы одно из пропущенных чисел. После того, как один проход через входной массив, только глядя на цифры от I , можно получить как сумму S и величину Q недостающих чисел от I . Мы делаем это, просто уменьшая длину I каждый раз, когда мы встречаем число от I (для получения Q ), и уменьшая предварительно вычисленную сумму всех чисел в I на это число каждый раз (для получения S ).
Теперь мы смотрим на S и Q . Если Q = 1 , то это означает , что тогда я содержит только один из недостающих чисел, и это число явно S . Мы помечаем I как завершенный (в программе это называется «однозначным») и оставляем его для дальнейшего рассмотрения. С другой стороны, если Q> 1 , мы можем вычислить среднее A = S / Q недостающих чисел содержится в I . Поскольку все числа различны, по крайней мере , один из таких чисел строго меньше , чем А и по меньшей мере один строго больше , чем A . Теперь мы разобьем I в Ана два меньших интервала, каждый из которых содержит хотя бы одно пропущенное число. Обратите внимание, что не имеет значения, какому из интервалов мы назначаем A, если это целое число.
Мы делаем следующий проход массива, вычисляя S и Q для каждого из интервалов отдельно (но в том же проходе), и после этого отмечаем интервалы с Q = 1 и разделяем интервалы с Q> 1 . Мы продолжаем этот процесс до тех пор, пока не появятся новые «неоднозначные» интервалы, то есть нам нечего делить, потому что каждый интервал содержит ровно одно пропущенное число (и мы всегда знаем это число, потому что мы знаем S ). Мы начинаем с единственного интервала «весь диапазон», содержащего все возможные числа (как [1..N] в вопросе).
Анализ сложности времени и пространства
Общее количество проходов p, которое мы должны сделать до тех пор, пока процесс не остановится, никогда не превысит число пропущенных чисел k . Неравенство p <= k может быть строго доказано. С другой стороны, существует также эмпирическая верхняя граница p <log 2 N + 3, которая полезна для больших значений k . Нам нужно выполнить бинарный поиск для каждого номера входного массива, чтобы определить интервал, к которому он принадлежит. Это добавляет множитель log k к сложности времени.
В общей сложности сложность по времени составляет O (N ≤ min (k, log N) ᛫ log k) . Обратите внимание, что для больших k это значительно лучше, чем для метода sdcvvc / Dimitris Andreou, который является O (N ᛫ k) .
Для своей работы алгоритм требует O (k) дополнительного пространства для хранения в большинстве k интервалов, что значительно лучше, чем O (N) в «битовых» решениях.
Реализация Java
Вот класс Java, который реализует вышеупомянутый алгоритм. Он всегда возвращает отсортированный массив пропущенных чисел. Кроме того, он не требует подсчета пропущенных чисел k, потому что вычисляет его при первом проходе. Весь диапазон чисел задается
minNumber
иmaxNumber
параметры (например , 1 и 100 для первого примера в вопросе).Справедливости ради, этот класс получает входные данные в виде
NumberBag
объектов.NumberBag
не разрешает изменение массива и произвольный доступ, а также подсчитывает, сколько раз массив был запрошен для последовательного обхода. Он также больше подходит для тестирования большого массива, чемIterable<Integer>
потому, что он позволяет избежатьint
упаковки примитивных значений и позволяет обернуть часть большогоint[]
для удобной подготовки теста. Это не трудно заменить, если это необходимо, сNumberBag
помощьюint[]
илиIterable<Integer>
введите вfind
подписи, изменив два для лупов в нем в Foreach из них.тесты
Простые примеры, демонстрирующие использование этих классов, приведены ниже.
Тестирование большого массива может быть выполнено следующим образом:
Попробуйте их на Ideone
источник
Я считаю , что у меня есть
O(k)
время иO(log(k))
алгоритм пространства, учитывая , что у вас естьfloor(x)
иlog2(x)
функция для произвольно больших целых доступных:У вас есть
k
длинное целое число -бит (отсюда иlog8(k)
пробел), куда вы добавляетеx^2
, где x - это следующее число, которое вы найдете в сумке:s=1^2+2^2+...
это занимаетO(N)
время (что не является проблемой для интервьюера). В конце вы получитеj=floor(log2(s))
самый большой номер, который вы ищете. Затемs=s-j
и вы делаете снова выше:Теперь у вас обычно нет функций floor и log2 для
2756
целых -битных чисел, а вместо этого для удвоений. Так? Проще говоря, для каждых 2 байтов (или 1, или 3, или 4) вы можете использовать эти функции, чтобы получить желаемые числа, но это добавляетO(N)
фактор к временной сложностиисточник
Это может звучать глупо, но в первой представленной вам проблеме вам нужно будет увидеть все оставшиеся числа в сумке, чтобы фактически сложить их, чтобы найти пропущенное число, используя это уравнение.
Так что, поскольку вы видите все числа, просто найдите пропавший номер. То же самое касается двух чисел. Довольно просто, я думаю. Нет смысла использовать уравнение, когда вы видите числа, оставшиеся в сумке.
источник
Я думаю, что это можно обобщить так:
Обозначим S, M в качестве начальных значений для суммы арифметических рядов и умножения.
Я должен подумать о формуле для расчета этого, но это не главное. В любом случае, если отсутствует один номер, вы уже предоставили решение. Однако, если два числа отсутствуют, давайте обозначим новую сумму и общее кратное через S1 и M1, которые будут следующими:
Поскольку вы знаете S1, M1, M и S, вышеприведенное уравнение разрешимо найти a и b, пропущенные числа.
Теперь для трех пропавших чисел:
Теперь ваше неизвестное равно 3, а у вас есть только два уравнения, из которых вы можете решить.
источник
M1 = M / (a * b)
(см. Этот ответ ). Тогда все работает нормально.Я не знаю, является ли это эффективным или нет, но я хотел бы предложить это решение.
4. Получите сумму пропущенных Nos с вашим обычным приближением формула суммы diff и скажем, что разница равна d.
Теперь запустите цикл, чтобы получить возможные пары (p, q), обе из которых лежат в [1, 100] и составляют сумму d.
Когда пара получена, проверьте, является ли (результат 3) XOR p = q и если да, то все готово.
Пожалуйста, поправьте меня, если я ошибаюсь, а также прокомментируйте сложность времени, если это правильно
источник
Мы можем делать Q1 и Q2 в O (log n) большую часть времени.
Предположим, что наш
memory chip
состоит из массиваn
числаtest tubes
. А числоx
в пробирке представленоx
milliliter
химически-жидким.Предположим, наш процессор является
laser light
. Когда мы зажигаем лазер, он проходит все трубки перпендикулярно его длине. Каждый раз, когда он проходит через химическую жидкость, светимость уменьшается на1
. И прохождение света на определенной миллилитровой отметке является операциейO(1)
.Теперь, если мы зажигаем наш лазер в середине пробирки и получаем светимость
n/2
.n/2
. Мы также можем проверить, уменьшается ли светимость1
или2
. если оно уменьшается,1
то одно пропущенное число меньше,n/2
а другое большеn/2
. Если оно уменьшается,2
то оба числа меньше, чемn/2
.Мы можем повторить вышеописанный процесс снова и снова, сузив нашу проблемную область. На каждом этапе мы уменьшаем домен вдвое. И, наконец, мы можем добраться до нашего результата.
Параллельные алгоритмы, которые стоит упомянуть (потому что они интересны),
O(log^3 n)
времени. И тогда пропущенный номер можно найти с помощью бинарного поиска поO(log n)
времени.n
процессоры, то каждый процесс может проверить один из входов и установить некоторый флаг, который идентифицирует число (удобно в массиве). И на следующем шаге каждый процесс может проверить каждый флаг и, наконец, вывести номер, который не помечен. Весь процесс займетO(1)
время. ЭтоO(n)
требует дополнительного места / памяти.Обратите внимание, что двум параллельным алгоритмам, представленным выше, может потребоваться дополнительное пространство, как указано в комментарии .
источник
O(logn)
на компьютере.N
и большеO(N)
(с точки зрения его зависимостиN
), которое мы намерены сделать лучше, чем.