В блестящем ответе caf каждое число, которое встречается k раз в массиве, выводится k-1 раз. Это полезное поведение, но вопрос, возможно, требует, чтобы каждый дубликат был напечатан только один раз, и он намекает на возможность сделать это, не нарушая границы линейного времени / постоянного пространства. Это можно сделать, заменив его второй цикл на следующий псевдокод:
for (i = 0; i < N; ++i) {
if (A[i] != i && A[A[i]] == A[i]) {
print A[i];
A[A[i]] = i;
}
}
Это использует свойство, которое после выполнения первого цикла, если какое-либо значение 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]
встречается более одного раза, а другое не встречается вообще.)
a[a[i]]
, а ограничение пространства O (1) намекает на то, чтоswap()
операция является ключевой.