Saya perlu menemukan kode (pseudo) yang efisien untuk menyelesaikan masalah berikut:
Mengingat dua urutan (tidak selalu berbeda) bilangan bulat (a[1], a[2], ..., a[n])
dan (b[1], b[2], ..., b[n])
, menemukan maksimum d
sehingga a[n-d+1] == b[1]
, a[n-d+2] == b[2]
, ..., dan a[n] == b[d]
.
Ini bukan pekerjaan rumah, saya benar-benar datang dengan ini ketika mencoba untuk mengontrak dua tensor sepanjang dimensi sebanyak mungkin. Saya menduga ada algoritma yang efisien (mungkin O(n)
?), Tetapi saya tidak dapat menemukan sesuatu yang tidak O(n^2)
. The O(n^2)
pendekatan akan loop jelas di d
kemudian loop batin pada item untuk memeriksa kondisi yang diperlukan sampai memukul maksimal d
. Tapi saya curiga ada yang lebih baik dari ini.
b[1] to b[d]
dan kemudian pergi ke arraya
menghitung hash untuka[1] to a[d]
jika itu cocok maka itu jawaban Anda, jika tidak menghitung hash untuka[2] to a[d+1]
dengan menggunakan kembali hash dihitung untuka[1] to a[d]
. Tapi saya tidak tahu apakah objek dalam array dapat menerima hash bergulir untuk dihitung pada mereka.a
dengan awalb
. Seperti ini .m
jumlah elemen dalama
, dann
jumlah elemen dalamb
. Sayangnya, saya tidak memiliki pengalaman yang cukup dengan KMP untuk memberi tahu Anda bagaimana menyesuaikannya.Jawaban:
Anda dapat memanfaatkan algoritma z , algoritma waktu linear ( O (n) ) yang:
Anda harus menyatukan array Anda ( b + a ) dan menjalankan algoritma pada array yang dihasilkan sampai saya yang pertama sehingga Z [i] + i == m + n .
Misalnya, untuk a = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0], gabungannya adalah [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3] yang akan menghasilkan Z [10] = 2 memenuhi Z [i] + i = 12 = m + n .
sumber
Untuk O (n) kompleksitas ruang / waktu, triknya adalah mengevaluasi hash untuk setiap urutan. Pertimbangkan array
b
:Dengan menggunakan metode Horner , Anda dapat mengevaluasi semua hash yang mungkin untuk setiap urutan. Pilih nilai dasar
B
(lebih besar dari nilai apa pun di kedua array Anda):Perhatikan bahwa Anda dapat mengevaluasi setiap urutan dalam waktu O (1), menggunakan hasil dari urutan sebelumnya, maka semua biaya pekerjaan O (n).
Sekarang Anda memiliki sebuah array
Hb = [h(b1), h(b2), ... , h(bn)]
, di manaHb[i]
hash darib1
hinggabi
.Lakukan hal yang sama untuk array
a
, tetapi dengan sedikit trik:Anda harus mencatat bahwa, ketika Anda melangkah dari satu urutan ke urutan lain, Anda mengalikan seluruh urutan sebelumnya dengan B dan menambahkan nilai baru dikalikan dengan B. Misalnya:
Sekarang Anda memiliki sebuah array
Ha = [h(an), h(an-1), ... , h(a1)]
, di manaHa[i]
hash dariai
hinggaan
.Sekarang, Anda dapat membandingkan
Ha[d] == Hb[d]
semuad
nilai dari n hingga 1, jika cocok, Anda memiliki jawaban.Ini berarti bahwa dua urutan berbeda mungkin memiliki hash yang sama, tetapi dua urutan yang sama akan selalu memiliki hash yang sama.
sumber
Ini memang bisa dilakukan dalam waktu linier, O (n) , dan O (n) ruang ekstra. Saya akan menganggap array input adalah string karakter, tetapi ini tidak penting.
Sebuah metode naif akan - setelah pencocokan k karakter yang sama - menemukan karakter yang tidak cocok, dan kembali k-1 unit di sebuah , ulang indeks di b , dan kemudian memulai proses pencocokan dari sana. Ini jelas merupakan kasus terburuk O (n²) .
Untuk menghindari proses pengulangan ini, kita dapat mengamati bahwa kembali tidak berguna jika kita belum menemukan karakter b [0] saat memindai karakter k-1 terakhir . Jika kita melakukan menemukan karakter itu, maka mundur ke posisi itu hanya akan berguna, jika dalam k berukuran substring kami memiliki pengulangan periodik.
Misalnya, jika kita melihat substring "abcabc" di suatu tempat di a , dan b adalah "abcabd", dan kami menemukan bahwa karakter terakhir dari b tidak cocok, kita harus mempertimbangkan bahwa pertandingan yang berhasil mungkin dimulai pada "a" yang kedua. di substring, dan kita harus memindahkan indeks saat ini di b kembali sesuai sebelum melanjutkan perbandingan.
Idenya adalah untuk melakukan beberapa preprocessing berdasarkan string b untuk login kembali-referensi dalam b yang berguna untuk memeriksa ketika ada ketidakcocokan. Jadi misalnya, jika b adalah "acaacaacd", kita dapat mengidentifikasi referensi-ulang berbasis-0 ini (letakkan di bawah setiap karakter):
Misalnya, jika kita memiliki yang sama dengan "acaacaaca" ketidakcocokan pertama terjadi pada karakter terakhir. Informasi di atas kemudian memberi tahu algoritma untuk kembali dalam b ke indeks 5, karena "acaac" adalah umum. Dan kemudian dengan hanya mengubah indeks saat ini di b kita dapat melanjutkan pencocokan pada indeks saat ini dari a . Dalam contoh ini, pencocokan karakter akhir kemudian berhasil.
Dengan ini kita dapat mengoptimalkan pencarian dan memastikan bahwa indeks dalam sebuah selalu bisa maju ke depan.
Berikut ini adalah implementasi dari gagasan itu dalam JavaScript, menggunakan sintaksis paling dasar dari bahasa itu saja:
Meskipun ada
while
loop bersarang , ini tidak memiliki lebih banyak iterasi total daripada n . Ini karena nilai k secara ketat menurun dalamwhile
tubuh, dan tidak bisa menjadi negatif. Ini hanya bisa terjadi ketikak++
dieksekusi yang berkali-kali memberi ruang yang cukup untuk penurunan tersebut. Jadi semuanya, tidak mungkin ada lebih banyak eksekusiwhile
tubuh daripadak++
eksekusi, dan yang terakhir jelas O (n).Untuk menyelesaikan, di sini Anda dapat menemukan kode yang sama seperti di atas, tetapi dalam cuplikan interaktif: Anda dapat memasukkan string Anda sendiri dan melihat hasilnya secara interaktif:
Tampilkan cuplikan kode
sumber