блискуча відповідь 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()операція є ключовою.