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

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

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


person Vaibhav    schedule 29.08.2008    source источник


Ответы (11)


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

In 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 с собой (более или менее). Единственный, кто остался без отметки, - это ваш недубликат.

person Kyle Cronin    schedule 29.08.2008
comment
это лучшее решение, если вы действительно можете выполнить XOR с элементами. То есть это зависит от типа данных. Я не уверен, сможете ли вы это сделать, если элементы являются строками. конечно, в этом случае ее можно решить с помощью еще одного уровня абстракции ... - person csmba; 11.09.2008
comment
Есть способы XORing строк путем XORing отдельных символов - вам просто нужно иметь временную переменную размером с самую большую строку. Что не сработает, так это попытка XOR связанного списка или какой-либо другой сложной структуры данных, но эта проблема связана просто с целыми числами. - person Kyle Cronin; 11.09.2008
comment
Вы можете сделать это с любым объектом, для которого также можно использовать криптографически безопасный хэш. Просто требуется второй проход, чтобы выяснить, какой элемент вы сопоставили. :) - person Nick Johnson; 25.09.2008
comment
Умное решение, но я думаю, что отрицательные числа могут немного его испортить. Вы могли потенциально выполнить XOR в маске, которая полностью отбросила остальные результаты вашей маскировки. - person Daniel Spiewak; 25.09.2008
comment
Отрицательное число - это битовое поле, как и положительное число. XOR все равно - person Airsource Ltd; 26.09.2008
comment
Конечно, вы должны задокументировать функцию как таковую, чтобы она работала только в том случае, если есть только один единственный номер, а остальные - в четных парах. Хотя мне очень нравится решение, которое вы опубликовали :) - person ; 21.01.2009
comment
@NickJohnson: Вам нужно не то, чтобы хеш был криптографически безопасным, а то, что он был идеальным, двусторонним или уникальным. У вас должна быть надежная возможность вернуться к объекту из хеша. - person Thomas Ahle; 27.12.2011
comment
Вау, эта функция волшебная: D ... в восторге! - person Luigi Massa Gallerano; 07.03.2013
comment
Это работает, поскольку ⊕ XOR ассоциативно, коммутативно и a ⊕ a = 0, a ⊕ 0 = a. В приведенном выше примере (((((3⊕2)⊕5)⊕2)⊕1)⊕5)⊕3 = ((((3⊕(2⊕5))⊕2)⊕1)⊕5)⊕3 (ассоциативный) = ((((3⊕(5⊕2))⊕2)⊕1)⊕5)⊕3 (коммутативный) = (((((3⊕5)⊕2)⊕2)⊕1)⊕5)⊕3 (ассоциативный) = ((((3⊕5)⊕(2⊕2))⊕1)⊕5)⊕3 (ассоциативный) = (((3⊕5)⊕1)⊕5)⊕3. Точно так же отменяются и другие, оставляя 1 в покое. - person legends2k; 29.10.2014
comment
Будет ли это работать, если повторяющееся значение появляется в списке нечетное количество раз? - person Robert; 18.05.2016
comment
@Robert Это сработает для определения числа, которое появляется в списке нечетное количество раз, при условии, что все остальные числа встречаются четное количество раз. - person Kyle Cronin; 18.05.2016
comment
Сначала необходимо упомянуть некоторый принцип: XOR не нужно сортировать, потому что он обратимый - person Jerry An; 07.02.2021

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

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

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

Бам.

person Tyler    schedule 29.08.2008
comment
Люблю это . Будет очень полезно, если все вопросы по алгоритму будут сопровождаться таким расширением. - person wanghq; 26.07.2015

Время O (N), время O (N)

HT = Хеш-таблица

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

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

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

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


комментарий: я не понимаю, как люди могут голосовать за решения, обеспечивающие производительность NLogN. в какой вселенной это «лучше»? Я еще более шокирован, что вы отметили принятый ответ как решение NLogN ...

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

person csmba    schedule 29.08.2008
comment
Я не вижу принятого ответа сейчас, мне интересно, как его не приняли. Между прочим, я бы отметил принятый ответ на основе ответов, доступных в то время. Кроме того, "принято" не означает "Лучшее" :) - person Vaibhav; 06.09.2008
comment
Ваш тоже не так хорош: он использует память O (n). - person user9282; 25.09.2008
comment
посмотрите на первую строку, выделенную жирным шрифтом: я прямо говорю, что это время O (N), память O (N), поэтому вы не критикуете мое предложение ни за что, на что я еще не указал. - person csmba; 26.09.2008
comment
Я думаю, вам пришлось расширить реализацию hash table как алгоритм, потому что составитель вопроса просил алгоритм, а не подходящую структуру данных. - person rook; 12.08.2013

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

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

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

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

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

person Ralph M. Rickenbach    schedule 03.09.2008
comment
Предложение @malach Кайл решает именно то, что говорится в постановке задачи. Нет смысла писать решение O (nlogn), которое защищает от недействительных данных, если существует решение O (n), а в формулировке проблемы не упоминается возможность того, что данные неверны. Во всяком случае, вот одна статья, которая объясняет решение немного подробнее с точки зрения теории информации: sysexpand.com/?path=exercises/number-appearing-once-in-array - person Zoran Horvat; 18.12.2013

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

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

person Farinha    schedule 29.08.2008

Реализация на 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
person SuperNova    schedule 14.09.2014

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

«Лучшее» - это субъективно, если вы не конкретизируете его.


При этом сказано:

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

person Jason Bunting    schedule 29.08.2008

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

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

Вопрос

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

person levand    schedule 29.08.2008

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

person chakrit    schedule 29.08.2008

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

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

person Community    schedule 01.09.2008

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

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
person hoyhoy    schedule 29.08.2008