Die brillante Antwort von caf druckt jede Zahl, die k-mal im Array k-1-mal erscheint. Das ist ein nützliches Verhalten, aber die Frage verlangt wohl, dass jedes Duplikat nur einmal gedruckt wird, und er spielt auf die Möglichkeit an, dies zu tun, ohne die linearen Grenzen von Zeit und konstantem Raum zu überschreiten. Dies kann erreicht werden, indem seine zweite Schleife durch den folgenden Pseudocode ersetzt wird:
for (i = 0; i < N; ++i) {
if (A[i] != i && A[A[i]] == A[i]) {
print A[i];
A[A[i]] = i;
}
}
Dies nutzt die Eigenschaft aus, dass nach dem Ausführen der ersten Schleife, wenn ein Wert m
mehr als einmal erscheint, garantiert ist, dass sich eine dieser Erscheinungen an der richtigen Position befindet, nämlich A[m]
. Wenn wir vorsichtig sind, können wir diesen "Heimatort" verwenden, um Informationen darüber zu speichern, ob noch Duplikate gedruckt wurden oder nicht.
In der Version von caf wurde beim Durchlaufen des Arrays A[i] != i
impliziert, dass A[i]
es sich um ein Duplikat handelt. In meiner Version verlasse ich mich auf eine etwas andere Invariante: Dies A[i] != i && A[A[i]] == A[i]
impliziert, dass A[i]
es sich um ein Duplikat handelt , das wir zuvor noch nicht gesehen haben . (Wenn Sie den Teil "den wir noch nicht gesehen haben" löschen, kann der Rest durch die Wahrheit der Invariante des Cafés und die Garantie, dass alle Duplikate eine Kopie an einem Heimatort haben, impliziert werden.) Diese Eigenschaft gilt für Der Anfang (nachdem die erste Runde des Cafés beendet ist) und ich zeigen unten, dass er nach jedem Schritt beibehalten wird.
Während wir das Array durchgehen, A[i] != i
impliziert der Erfolg des Tests, dass es A[i]
sich um ein Duplikat handeln kann, das zuvor noch nicht gesehen wurde. Wenn wir es noch nicht gesehen haben, erwarten wir, dass A[i]
der Heimatort auf sich selbst zeigt - darauf wird in der zweiten Hälfte der if
Erkrankung getestet . Wenn dies der Fall ist, drucken wir es aus und ändern den Heimatort, um auf dieses zuerst gefundene Duplikat zu verweisen. Dadurch wird ein zweistufiger "Zyklus" erstellt.
Um zu sehen, dass diese Operation unsere Invariante nicht verändert, nehmen wir an, dass m = A[i]
eine bestimmte Position i
zufriedenstellend ist A[i] != i && A[A[i]] == A[i]
. Es ist offensichtlich, dass die von uns vorgenommene Änderung ( A[A[i]] = i
) dazu beiträgt, zu verhindern, dass andere Ereignisse außerhalb des Hauses m
als Duplikate ausgegeben werden, indem die zweite Hälfte ihrer if
Bedingungen fehlschlägt. Funktioniert sie jedoch, wenn sie i
am Heimatort eintrifft m
? Ja, das wird es, denn jetzt, obwohl i
wir bei dieser neuen Version feststellen, dass die erste Hälfte der if
Bedingung A[i] != i
wahr ist, prüft die zweite Hälfte, ob der Ort, auf den sie zeigt, ein Heimatort ist, und stellt fest, dass dies nicht der Fall ist. In dieser Situation wissen wir nicht mehr, ob der doppelte Wert war m
oder A[m]
war, aber wir wissen das so oder so,Es wurde bereits berichtet , da diese 2 Zyklen garantiert nicht im Ergebnis der ersten Schleife des Cafés erscheinen. (Beachten Sie, dass wenn m != A[m]
dann genau eines von m
und A[m]
mehrmals vorkommt und das andere überhaupt nicht vorkommt.)
a[a[i]]
, und die O (1) -Raumbeschränkung deuten darauf hin, dass dieswap()
Operation der Schlüssel ist.