Поиск единственного числа в списке

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

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

Ответ 1

Самый быстрый способ (O (n)) и наиболее эффективный по памяти (O (1)) - это операция XOR.

В C:

int arr[] = {3, 2, 5, 2, 1, 5, 3};

int num = 0, i;

for (i=0; i < 7; i++)
    num ^= arr[i];

printf("%i\n", num);

Это печатает "1", который является единственным, который встречается один раз.

Это работает, потому что в первый раз, когда вы нажмете число, он помечает переменную num с самим собой, а во второй раз она не маркирует num с самим собой (более или менее). Единственный, который остается немаркированным, - это ваш не дубликат.

Ответ 2

Кстати, вы можете расширить эту идею, чтобы очень быстро найти два уникальных номера среди списка дубликатов.

Позвольте назвать уникальные числа a и b. Сначала возьмите XOR всего, как предложил Кайл. Мы получаем a ^ b. Мы знаем a ^ b!= 0, так как a!= B. Выберите один бит a ^ b и используйте его как маску - более подробно: выберите x как мощность 2, чтобы x и (a ^ b) были ненулевыми.

Теперь разделим список на два подписок - один подписок содержит все числа y с y & x == 0, а остальные - в другом подсписке. Кстати, мы выбрали x, мы знаем, что a и b находятся в разных ведрах. Мы также знаем, что каждая пара дубликатов по-прежнему находится в одном ковше. Таким образом, мы можем применить старый метод "XOR-em-all" к каждому ведру независимо друг от друга и обнаружить, что a и b полностью.

Bam.

Ответ 3

O (N) время, O (N) память

HT = таблица хешей

HT.clear() перейдите по списку в порядке для каждого элемента, который вы видите

if(HT.Contains(item)) -> HT.Remove(item)
else
ht.add(item)

в конце, элемент в HT - это элемент, который вы ищете.

Примечание (credit @Jared Updike): эта система найдет все нечетные экземпляры элементов.


комментарий. Я не вижу, как люди могут проголосовать за решения, которые дают вам производительность NLogN. в которой вселенная "лучше"? Я еще более шокирован, что вы отметили принятый ответ s NLogN solution...

Я согласен с тем, что если требуется постоянная память, то NLogN будет (пока) лучшим решением.

Ответ 4

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

Если бы было несколько одиночных значений или тройки, результат также был бы неработоспособным.

Тестирование набора данных вполне может привести к более дорогостоящему алгоритму либо в памяти, либо во времени.

Решение Csmba показывает некоторые данные об ошибке (не более одного значения вхождения), но не другие (квадранты). Что касается его решения, то в зависимости от реализации HT, память и/или время больше, чем O (n).

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

Ответ 5

Я бы сказал, что использование алгоритма сортировки, а затем просмотр отсортированного списка для поиска номера - это хороший способ сделать это.

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

Ответ 6

Реализация в Ruby:

a = [1,2,3,4,123,1,2,.........]
t = a.length-1
for i in 0..t
   s = a.index(a[i])+1
   b = a[s..t]
   w = b.include?a[i]
   if w == false
       puts a[i]
   end
end

Ответ 7

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

"Лучшее" субъективно, если вы не более конкретны.


Это сказало:

Итерации по номерам, для каждого номера найдите список для этого числа, и когда вы достигнете числа, которое возвращает только 1 для количества результатов поиска, вы сделали.

Ответ 8

Похоже, лучшее, что вы можете сделать, это перебрать список, поскольку каждый элемент добавляет его в список "замеченных" элементов или удаляет его из "увиденного", если он уже существует, и в конце списка из "замеченных" предметов будет включать сингулярный элемент. Это O (n) по времени и n относительно пространства (в худшем случае будет намного лучше, если список будет отсортирован).

Тот факт, что они являются целыми числами, не имеет особого значения, поскольку нет ничего особенного, что вы можете сделать с их добавлением... есть?

Вопрос

Я не понимаю, почему выбранный ответ "наилучший" по любому стандарту. O (N * lgN) > O (N), и он меняет список (или создает его копию, которая еще дороже в пространстве и времени). Я что-то пропустил?

Ответ 9

В зависимости от того, насколько велики/малы/разнородны числа. Может быть применена сортировка радиуса, которая в значительной степени уменьшит время сортировки решения O (N log N).

Ответ 10

Метод сортировки и метод XOR имеют одинаковую временную сложность. Метод XOR - это только O (n), если вы считаете, что побитовое XOR двух строк является постоянной операцией времени. Это эквивалентно утверждению, что размер целых чисел в массиве ограничен константой. В этом случае вы можете использовать сортировку Radix для сортировки массива в O (n).

Если числа не ограничены, то побитовое XOR принимает время O (k), где k - длина битовой строки, а метод XOR принимает O (nk). Теперь снова сортировка Radix будет сортировать массив по времени O (nk).

Ответ 11

Вы можете просто поместить элементы в набор в хэш, пока не найдете столкновение. В рубине это однострочный.

def find_dupe(array)
  h={}
  array.detect { |e| h[e]||(h[e]=true; false) }
end

Итак, find_dupe([1,2,3,4,5,1]) вернется 1.

Это на самом деле общий вопрос об "трюке". Обычно это список последовательных целых чисел с одним дубликатом. В этом случае интервьюер часто ищет, чтобы вы использовали гауссовскую сумму трюков n-целых чисел, например. n*(n+1)/2 вычитается из фактической суммы. Ответ учебника примерно такой.

def find_dupe_for_consecutive_integers(array)
  n=array.size-1   # subtract one from array.size because of the dupe
  array.sum - n*(n+1)/2
end