Это то, что я придумал, для чего не требуется дополнительный знаковый бит:
for i := 0 to n - 1
while A[A[i]] != A[i]
swap(A[i], A[A[i]])
end while
end for
for i := 0 to n - 1
if A[i] != i then
print A[i]
end if
end for
Первый цикл переставляет массив так, что если элемент x
присутствует хотя бы один раз, то одна из этих записей будет в позиции A[x]
.
Обратите внимание, что на первый взгляд он может не выглядеть O (n), но это так - хотя у него есть вложенный цикл, он все равно выполняется во O(N)
времени. Обмен происходит только в том случае, если есть i
такой A[i] != i
, и каждый обмен устанавливает по крайней мере один элемент такой A[i] == i
, где раньше этого не было. Это означает, что общее количество свопов (и, следовательно, общее количество выполнений while
тела цикла) не больше N-1
.
Второй цикл печатает значения, x
для которых A[x]
не равно x
- поскольку первый цикл гарантирует, что если x
существует хотя бы один раз в массиве, один из этих экземпляров будет в A[x]
, это означает, что он печатает эти значенияx
которых нет в массив.
(Ссылка на Ideone, чтобы вы могли с ней поиграть)
a[a[i]]
, а ограничение пространства O (1) намекает на то, чтоswap()
операция является ключевой.5
он не входит в диапазон0..N-1
(N
в данном случае5
).print
инструкцииprint i
превращает это в решение для stackoverflow.com/questions/5249985/… и (при условии, что «мешок» является изменяемым массивом) Qk stackoverflow.com/questions/3492302/… .В блестящем ответе caf каждое число, которое встречается k раз в массиве, выводится k-1 раз. Это полезное поведение, но вопрос, возможно, требует, чтобы каждый дубликат был напечатан только один раз, и он намекает на возможность сделать это, не нарушая границы линейного времени / постоянного пространства. Это можно сделать, заменив его второй цикл на следующий псевдокод:
Это использует свойство, которое после выполнения первого цикла, если какое-либо значение
m
появляется более одного раза, то одно из этих появлений гарантированно находится в правильной позиции, а именноA[m]
. Если мы будем осторожны, мы можем использовать это «домашнее» местоположение для хранения информации о том, были ли еще напечатаны дубликаты или нет.В версии caf, когда мы просматривали массив,
A[i] != i
подразумевается, чтоA[i]
это дубликат. В своей версии я полагаюсь на немного другой инвариант: этоA[i] != i && A[A[i]] == A[i]
означает, чтоA[i]
это дубликат , которого мы раньше не видели. . (Если вы отбросите часть «что мы не видели раньше», то можно увидеть, что остальное подразумевается истинностью инварианта caf и гарантией того, что все дубликаты имеют некоторую копию в домашнем местоположении.) Это свойство сохраняется в начало (после завершения 1-го цикла caf), и я покажу ниже, что оно сохраняется после каждого шага.Когда мы проходим через массив, успех со
A[i] != i
стороны теста означает, что этоA[i]
может быть дубликат, которого раньше не было. Если мы не видели этого раньше, то мы ожидаем, чтоA[i]
домашнее местоположение будет указывать на себя - это то, что проверено во второй половинеif
условия. Если это так, мы распечатываем его и изменяем домашнее местоположение, чтобы оно указывало обратно на этот первый найденный дубликат, создавая двухэтапный «цикл».Для того, чтобы увидеть , что эта операция не изменяет наш инвариант, предположит , что
m = A[i]
для определенной позиции ,i
удовлетворяющейA[i] != i && A[A[i]] == A[i]
. Очевидно, что изменение, которое мы делаем (A[A[i]] = i
), будет работать, чтобы предотвратитьm
вывод других не-домашних вхождений в качестве дубликатов, вызываяif
сбой второй половины их условий, но будет ли оно работать, когда будетi
достигнуто домашнее местоположениеm
,? Да, будет, потому что теперь, хотя в этом новомi
мы обнаруживаем, что первая половинаif
условияA[i] != i
истинна, вторая половина проверяет, является ли местоположение, на которое оно указывает, домашним местоположением, и обнаруживает, что это не так. В этой ситуации мы уже не знаем , является лиm
илиA[m]
был повторяющееся значение, но мы знаем , что так или иначе,об этом уже сообщалось , потому что эти 2 цикла гарантированно не появятся в результате 1-го цикла caf. (Обратите внимание, что еслиm != A[m]
тогда ровно одно изm
иA[m]
встречается более одного раза, а другое не встречается вообще.)источник
Вот псевдокод
Пример кода на C ++
источник
-
на~
для нулевой проблемы.O(n)
кроется проблема, но технически он использует скрытое пространство -n
биты знака. Если массив определен таким образом, что каждый элемент может содержать значения только между0
иn-1
, то он явно не работает.Для относительно небольшого N мы можем использовать операции div / mod
Не C / C ++, но все равно
http://ideone.com/GRZPI
источник
Не очень красиво, но, по крайней мере, легко увидеть свойства O (N) и O (1). В основном мы сканируем массив и для каждого числа видим, была ли соответствующая позиция помечена как уже виденная один раз (N) или уже увиденная несколько раз (N + 1). Если он помечен как «уже просмотренный один раз», мы распечатываем его и помечаем его как «уже просмотренный несколько раз». Если он не отмечен флажком, мы отмечаем его как «уже просмотренный один раз» и перемещаем исходное значение соответствующего индекса в текущую позицию (установка флажка является деструктивной операцией).
или еще лучше (быстрее, несмотря на двойной цикл):
источник
if (value > i) a[i--] = a[value];
работает: еслиvalue <= i
тогда мы уже обработали значение ata[value]
и можем безопасно его перезаписать. Также я бы не сказал, что природа O (N) очевидна! Объяснение: основной цикл выполняетсяN
раз, плюс сколько раз выполняетсяa[i--] = a[value];
строка. Эта строка может запускаться только в том случаеa[value] < N
, и каждый раз, когда она запускается, сразу же после этого значение массива, которое еще не былоN
установлено вN
, поэтому она может выполняться в большинствеN
случаев, всего не более чем2N
итераций цикла.Одно из решений в C:
Это время O (n) и сложность пространства O (1).
источник
Предположим, что мы представляем этот массив как структуру данных однонаправленного графа - каждое число является вершиной, а его индекс в массиве указывает на другую вершину, образующую ребро графа.
Для еще большей простоты у нас есть индексы от 0 до n-1 и диапазон чисел от 0..n-1. например
0 (3) -> 3 (3) - это цикл.
Ответ: Просто обойдите массив, опираясь на индексы. если a [x] = a [y], то это цикл и, следовательно, дубликат. Перейти к следующему индексу и продолжить снова и так далее до конца массива. Сложность: O (n) время и O (1) пространство.
источник
Крошечный код на Python для демонстрации описанного выше метода caf:
источник
i
значения может произойти более одного раза - обратите внимание наwhile
мой ответ.Алгоритм легко увидеть в следующей функции C. Получение исходного массива, хотя и не требуется, будет возможно, взяв каждую запись по модулю n .
Ideone Link для тестирования.
источник
источник
Я быстро создал один образец приложения для игровой площадки для поиска дубликатов с временной сложностью 0 (n) и постоянным дополнительным пространством. Пожалуйста, проверьте URL-адрес Поиск дубликатов
IMP Вышеупомянутое решение работало, когда массив содержит элементы от 0 до n-1, причем любое из этих чисел встречается любое количество раз.
источник
источник
Если массив не слишком велик, это решение проще. Он создает другой массив того же размера для отметки.
1 Создайте растровое изображение / массив того же размера, что и ваш входной массив
2 просканируйте ваш входной массив и увеличьте его счетчик в указанном выше массиве
3 Теперь просканируйте массив check_list и распечатайте дубликат либо один раз, либо столько раз, сколько они были дублированы.
Конечно, это занимает вдвое больше места, чем занимает решение, указанное выше, но эффективность по времени составляет O (2n), что в основном составляет O (n).
источник
O(1)
космос.