В блестящем ответе 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()операция является ключевой.