блискуча відповідь 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]
. Якщо ми обережні, ми можемо використовувати це "домашнє" місце для зберігання інформації про те, чи були надруковані ще дублікати.
У версії кафе, коли ми проходили масив, A[i] != i
мається на увазі, що A[i]
це дублікат. У своїй версії я покладаюся на дещо інший інваріант: це A[i] != i && A[A[i]] == A[i]
означає, що A[i]
це дублікат, якого ми ще не бачили . (Якщо ви скинете частину "того, чого ми раніше не бачили", решта може бути зрозуміла як істина інваріантності кафе, і гарантія того, що всі копії мають певну копію в домашньому місці.) Ця властивість зберігається у початку (після завершення 1-ї петлі кафе), і я показую, що він підтримується після кожного кроку.
Коли ми проходимо масив, успіх A[i] != i
тесту передбачає, що він A[i]
може бути дублікатом, якого раніше не бачили. Якщо ми цього ще не бачили, ми очікуємо A[i]
, що місце розташування будинку вкаже на себе - це те, що перевірено другою половиною if
умови. Якщо це так, ми роздруковуємо його та змінюємо домашнє місце, щоб вказувати на цей перший знайдений дублікат, створюючи 2-кроковий "цикл".
Щоб побачити, що ця операція не змінює наш інваріант, припустимо, m = A[i]
для певної позиції i
задовольняє A[i] != i && A[A[i]] == A[i]
. Очевидно, що зміна, яку ми вносимо ( A[A[i]] = i
), буде працювати, щоб запобігти появі інших неприбуткових випадків m
виведення в якості дублікатів, спричинивши if
збій 2-ї половини їхніх умов, але чи спрацює вона, коли i
приїде в домашнє місцезнаходження m
,? Так, так, адже зараз, навіть якщо в цьому новому i
ми виявимо, що перша половина if
умови A[i] != i
є справжньою, друга половина перевіряє, чи є місце, на яке вона вказує, - це домашнє місце та виявляє, що це не так. У цій ситуації ми вже не знаємо , є чи m
або A[m]
був повторюється значення, але ми знаємо , що так чи інакше,вже повідомлялося , оскільки ці 2-цикли гарантовано не з’являться в результаті 1-го циклу кафе. (Зверніть увагу, що якщо m != A[m]
тоді саме один з m
і A[m]
трапляється не один раз, а інший не виникає взагалі.)
a[a[i]]
, а обмеження простору O (1) натякає на те, щоswap()
операція є ключовою.