Jawaban brilian caf mencetak setiap angka yang muncul k kali dalam larik k-1 kali. Itu perilaku yang berguna, tetapi pertanyaannya bisa dibilang memanggil setiap duplikat untuk dicetak sekali saja, dan dia menyinggung kemungkinan melakukan ini tanpa meniup batas ruang waktu / konstan linier. Ini dapat dilakukan dengan mengganti loop keduanya dengan pseudocode berikut:
for (i = 0; i < N; ++i) {
if (A[i] != i && A[A[i]] == A[i]) {
print A[i];
A[A[i]] = i;
}
}
Ini mengeksploitasi properti yang setelah loop pertama dijalankan, jika ada nilai yang m
muncul lebih dari satu kali, maka salah satu kemunculan tersebut dijamin berada pada posisi yang benar, yaituA[m]
. Jika kita berhati-hati kita dapat menggunakan lokasi "rumah" itu untuk menyimpan informasi tentang apakah ada duplikat yang telah dicetak atau belum.
Dalam versi caf, saat kita menelusuri array, A[i] != i
tersirat bahwa itu A[i]
adalah duplikat. Dalam versi saya, saya mengandalkan invarian yang sedikit berbeda: yang A[i] != i && A[A[i]] == A[i]
menyiratkan bahwa itu A[i]
adalah duplikat yang belum pernah kita lihat sebelumnya . (Jika Anda membuang bagian "yang belum pernah kami lihat sebelumnya", sisanya dapat dilihat tersirat oleh kebenaran dari ketidak-beraturan kafe, dan jaminan bahwa semua duplikat memiliki beberapa salinan di lokasi rumah.) Properti ini berlaku di permulaan (setelah loop pertama kafe selesai) dan saya tunjukkan di bawah bahwa itu dipertahankan setelah setiap langkah.
Saat kita menelusuri larik, keberhasilan pada A[i] != i
bagian pengujian menyiratkan bahwa A[i]
bisa jadi duplikat yang belum pernah terlihat sebelumnya. Jika kita belum pernah melihatnya sebelumnya, maka kita berharap A[i]
lokasi rumah mengarah ke dirinya sendiri - itulah yang diuji pada paruh kedua if
kondisi tersebut. Jika demikian, kami mencetaknya dan mengubah lokasi rumah agar mengarah kembali ke duplikat yang pertama ditemukan ini, membuat "siklus" 2 langkah.
Untuk melihat bahwa operasi ini tidak mengubah invarian kami, anggaplah m = A[i]
untuk posisi tertentu i
memuaskan A[i] != i && A[A[i]] == A[i]
. Jelas bahwa perubahan yang kita buat ( A[A[i]] = i
) akan berfungsi untuk mencegah kejadian non-rumah lainnya m
menjadi keluaran sebagai duplikat dengan menyebabkan paruh kedua dari if
kondisi mereka gagal, tetapi apakah itu akan berfungsi ketika i
tiba di lokasi rumah m
,? Ya itu akan, karena sekarang, meskipun pada yang baru ini i
kami menemukan bahwa paruh pertama dari if
kondisi, A[i] != i
benar, paruh ke-2 menguji apakah lokasi yang ditunjuknya adalah lokasi rumah dan ternyata bukan. Dalam situasi ini kami tidak lagi mengetahui apakah nilai duplikat m
atau A[m]
merupakan nilai duplikat, tetapi kami tahu bahwa bagaimanapun juga,sudah dilaporkan , karena 2 siklus ini dijamin tidak akan muncul di hasil loop pertama kafe. (Perhatikan bahwa jika m != A[m]
maka tepat satu dari m
dan A[m]
terjadi lebih dari sekali, dan yang lainnya tidak terjadi sama sekali.)
a[a[i]]
, dan batasan ruang O (1) mengisyaratkanswap()
operasi menjadi kuncinya.