Найти число с четным числом вхождений

Для массива, где число вхождений каждого числа нечетно, кроме одного числа, число вхождений которого четное. Найдите число с четными вхождениями.

например.

1, 1, 2, 3, 1, 2, 5, 3, 3

Вывод должен быть:

2

Ниже приведены ограничения:

  • Номера не находятся в зоне действия.
  • Сделайте это на месте.
  • Требуемая сложность по времени - O (N).
  • Массив может содержать отрицательные числа.
  • Массив не сортируется.

С приведенными выше ограничениями все мои мысли потерпели неудачу: сортировка на основе сравнения, подсчет сортировки, BST, хеширование, грубая сила.

Мне интересно узнать: будет ли XORing работать здесь? Если да, то как?

Ответы

Ответ 1

Эта проблема занимала мои поездки на метро в течение нескольких дней. Вот мои мысли.

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

Две крайние подзадачи, которые приходят на ум, следующие:

  • Массив содержит два значения: одно из них повторяется четное число раз, а другое повторяется нечетное число раз.
  • Массив содержит n-1 различные значения: все значения присутствуют один раз, за ​​исключением одного значения, которое присутствует два раза.

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

Если мы предположим, что число различных значений равно O (1), каждый массив будет иметь m разные значения, m не зависит от n. В этом случае мы могли бы прокручивать исходный массив, стирая и подсчитывая вхождения каждого значения. В этом примере он даст

1, 1, 2, 3, 1, 2, 5, 3, 3 -> First value is 1 so count and erase all 1
2, 3, 2, 5, 3, 3 -> Second value is 2, count and erase
-> Stop because 2 was found an even number of times.

Это решит первый экстремальный пример со сложностью O(mn), который оценивается как O(n).

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

второй крайний случай будет состоять в нахождении единственного повторяющегося значения внутри массива. Это кажется невозможным в O(n), но есть особые случаи, когда мы можем: если массив имеет n элементы и значения внутри, это {1, n-1} + повторное значение (или некоторый вариант, как и все числа между x и y). В этом случае мы суммируем все значения, вычитаем n(n-1)/2 из суммы и получаем повторяющееся значение.

Решение второго крайнего случая со случайными значениями внутри массива или общий случай, когда m не является константой на n, в постоянной памяти и O(n) время мне кажется невозможным.

Замечание: здесь XORing не работает, потому что число, которое мы хотим, появляется четное количество раз, а другие появляются нечетное число раз. Если проблема заключалась в том, чтобы "указать число, которое появляется нечетным числом раз, все остальные числа появляются четным числом раз", мы могли бы XOR все значения и находить нечетный в конце.

Мы могли бы попытаться найти метод с использованием этой логики: нам понадобится что-то вроде функции, которая применяла нечетное число раз к числу, дала бы 0, и четное число раз было бы идентичным. Не думайте, что это возможно.

Ответ 2

Введение

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

Во-первых, нам нужно определить несколько неясных деталей проблемы. Требуемая временная сложность O(N), но что такое N? Большинство комментаторов полагают, что N - количество элементов в массиве. Это было бы хорошо, если бы числа в массиве имели фиксированный максимальный размер, и в этом случае решение решетки Михаэля Гс решило проблему. Но, я интерпретирую ограничение №1, в отсутствие пояснения оригинальным плакатом, так как максимальное количество цифр не нужно фиксировать. Поэтому, если N (нижний регистр) - это число элементов в массиве, а m средняя длина элементов, то общий размер входного файла, подлежащий обсуждению, равен mn. Нижняя граница времени решения O(mn), поскольку это время считывания ввода, необходимого для проверки решения. Итак, мы хотим, чтобы решение было линейным относительно общего размера ввода N = nm.

Например, мы могли бы иметь n = m, то есть sqrt(N) элементы sqrt(N) средней длины. Сортировка сравнения принимает операции O( log(N) sqrt(N) ) < O(N), но это не победа, потому что сами операции в среднем принимают O(m) = O(sqrt(N)) время, поэтому мы вернулись к O( N log(N) ).

Кроме того, для сортировки radix будет O(mn) = O(N), если m была максимальной длиной вместо средней длины. Максимальная и средняя длина будут в том же порядке, если предполагается, что числа попадают в некоторый ограниченный диапазон, но если нет, мы можем иметь небольшой процент с большим и переменным числом цифр и большим процентом с небольшим количеством цифр, Например, 10% чисел могут иметь длину m^1.1 и 90% длины m*(1-10%*m^0.1)/90%. Средняя длина будет m, но максимальная длина m^1.1, поэтому сортировка счисления будет O(m^1.1 n) > O(N).

Чтобы какая-либо проблема заключалась в том, что я слишком сильно изменил определение проблемы, моя цель - описать алгоритм с временной сложностью, линейной по количеству элементов, то есть O(N). Но мне также потребуется выполнить операции линейной временной сложности по длине каждого элемента, так что в среднем по всем элементам эти операции будут O(m). Эти операции будут умножением и добавлением, необходимым для вычисления хеш-функций для элементов и сравнения. И если действительно это решение решает проблему в O(N) = O(nm), это должно быть оптимальной сложностью, поскольку для проверки ответа требуется одно и то же время.

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

Возможное решение

Во-первых, ограничение, что могут быть отрицательные числа, является пустым. За один проход через данные мы записываем минимальный элемент z и количество элементов N. На втором проходе мы добавим (3-z) к каждому элементу, поэтому наименьший элемент теперь равен 3. (Обратите внимание, что в результате число переполнений может переполняться, поэтому мы должны делать постоянное количество дополнительных проходов через данные сначала проверить их на решения.) Как только мы получим наше решение, мы просто вычтем (3-z), чтобы вернуть его в исходную форму. Теперь у нас есть три специальных значения маркера 0, 1 и 2, которые сами по себе не являются элементами.

Шаг 1

Используйте алгоритм выбора медианных медианов, чтобы определить элемент 90-го процентиля, p, массива A и раздела массив в набор двух наборов S и T, где S имеет элементы 10% of n, превышающие p, а T имеет элементы меньше p. Это занимает шаги O(N) (с шагом, принимающим O(m) в среднем за время O(N) всего). Соответствие элементов p может быть помещено либо в S, либо T, но для простоты пройдите через массив один раз и протестируйте p и устраните его, заменив его на 0. Set S первоначально охватывает индексы 0..s, где S составляет около 10% of N, а set T охватывает оставшиеся 90% индексов s+1..n.

Шаг 2

Теперь мы будем проходить через i in 0..s и для каждого элемента e_i мы собираемся вычислить хеш-функцию h(e_i) в s+1..n. Хорошо использовать универсальное хэширование, чтобы получить равномерное распределение. Таким образом, наша хеширующая функция будет делать умножение и сложение и принимать линейное время для каждого элемента по отношению к его длине.

Хорошо использовать модифицированную стратегию линейного зондирования для столкновений:

  • h(e_i) занят членом T (что означает A[ h(e_i) ] < p, но не является маркером 1 или 2)) или является 0. Это хэш-таблица. Вставьте e_i путем замены элементов из слотов i и h(e_i).

  • h(e_i) занят членом S (что означает A[ h(e_i) ] > p) или маркерами 1 или 2. Это столкновение с хэш-таблицей. Проведите линейное зондирование до тех пор, пока не встретите дубликат e_i или член T или 0.

    • Если член T, это снова ошибка хэш-таблицы, поэтому вставьте e_i, как в (1.), путем замены на слот i.

    • Если дубликат e_i, это хэш-таблица. Изучите следующий элемент. Если этот элемент 1 или 2, мы уже видели e_i уже несколько раз, изменим 1 на 2 и наоборот, чтобы отслеживать его изменение в четности. Если следующий элемент не 1 или 2, то мы увидели только e_i один раз раньше. Мы хотим сохранить 2 в следующий элемент, чтобы показать, что weve теперь видел e_i четное число раз. Мы ищем следующий "пустой" слот, который занят членом T, который хорошо перемещается в слот i, или 0, и сдвигает элементы обратно на индекс h(e_i)+1 вниз, поэтому у нас есть комната рядом с h(e_i) для хранения нашей информации о четности. Заметьте, что нам больше не нужно хранить e_i, поэтому мы не использовали лишнее пространство.

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

Шаг 3

Как только мы закончили хеширование элементов 0..s в s+1..n, мы пройдем s+1..n. Если мы найдем элемент из S, за которым следует 2, это наш элемент цели, и мы закончили. Любой элемент e of S, за которым следует другой элемент S, указывает, что e встречается только один раз и может быть обнулен. Аналогично e, а затем 1 означает, что мы видели e нечетное число раз, и мы можем обнулить e и маркер 1.

Промыть и повторить по желанию

Если мы не нашли элемент цели, повторим этот процесс. Наш раздел 90-го процентиля переместит 10% N оставшихся наибольших элементов в начало A и остальных элементов, включая пустые слоты 0 -marker до конца. Мы продолжаем по-прежнему с хэшированием. Мы должны делать это максимум 10 раз, каждый раз обрабатывая 10% N.

Заключительный анализ

Разделение по алгоритму медианы медианов имеет временную сложность O(N), которую мы делаем 10 раз, еще O(N). Каждая хеш-операция занимает в среднем O(1), так как нагрузка хэш-таблицы низкая, и в целом выполняется хэш-операций (около 10% от n для каждого из 10 повторений). Каждый из элементов N имеет рассчитанную для них хеш-функцию со сложностью по времени, линейной по длине, поэтому в среднем по всем элементам O(m). Таким образом, хеширующие операции в совокупности составляют O(mn) = O(N). Итак, если бы я проанализировал это правильно, то в целом этот алгоритм O(N)+O(N)=O(N). (Это также O(N), если операции сложения, умножения, сравнения и подкачки считаются постоянными по отношению к вводу.)

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

Ответ 3

См. следующую статью: Алгоритм сортировки, который выполняется во времени O (n), а также сортируется на месте, предполагая, что максимальное число цифр является постоянным, мы можем сортировать массив на месте в O (n) времени.

После этого речь идет о подсчете числа номеров, которые будут занимать среднее время n/2, чтобы найти одно число, число случаев которого равно.