Apakah ada algoritma yang efisien selain pencarian brute force untuk menemukan tiga bilangan bulat?
Ya; kita bisa menyelesaikan ini dalam waktu O (n 2 )! Pertama, pertimbangkan bahwa masalah Anda P
dapat diungkapkan dengan cara yang sedikit berbeda sehingga menghilangkan kebutuhan akan "nilai target":
masalah asli P
: Mengingat sebuah array A
dari n
integer dan nilai target S
, apakah terdapat 3-tupel dari A
jumlah itu untuk S
?
dimodifikasi masalah P'
: Mengingat sebuah array A
dari n
bilangan bulat, tidak terdapat 3-tupel dari A
jumlah yang ke nol?
Perhatikan bahwa Anda dapat pergi dari versi ini masalah P'
dari P
dengan mengurangkan S Anda / 3 dari setiap elemen dalam A
, tapi sekarang Anda tidak perlu nilai target lagi.
Jelas, jika kita hanya menguji semua 3-tupel yang mungkin, kita akan menyelesaikan masalah di O (n 3 ) - itulah garis dasar brute-force. Apakah mungkin berbuat lebih baik? Bagaimana jika kita memilih tuple dengan cara yang agak lebih pintar?
Pertama, kami menginvestasikan waktu untuk mengurutkan array, yang dikenakan biaya penalti awal O (n log n). Sekarang kita jalankan algoritma ini:
for (i in 1..n-2) {
j = i+1 // Start right after i.
k = n // Start at the end of the array.
while (k >= j) {
// We got a match! All done.
if (A[i] + A[j] + A[k] == 0) return (A[i], A[j], A[k])
// We didn't match. Let's try to get a little closer:
// If the sum was too big, decrement k.
// If the sum was too small, increment j.
(A[i] + A[j] + A[k] > 0) ? k-- : j++
}
// When the while-loop finishes, j and k have passed each other and there's
// no more useful combinations that we can try with this i.
}
Algoritma ini bekerja dengan menempatkan tiga pointer, i
, j
, dan k
pada berbagai titik dalam array. i
dimulai dari awal dan perlahan-lahan bekerja sampai akhir. k
menunjuk ke elemen terakhir. j
menunjuk ke tempat i
dimulainya. Kami berulang mencoba untuk menjumlahkan elemen pada indeks masing-masing, dan setiap kali salah satu dari berikut terjadi:
- Jumlahnya tepat! Kami telah menemukan jawabannya.
- Jumlahnya terlalu kecil. Bergerak
j
lebih dekat ke ujung untuk memilih nomor terbesar berikutnya.
- Jumlahnya terlalu besar. Bergerak
k
lebih dekat ke awal untuk memilih nomor terkecil berikutnya.
Untuk masing-masing i
, petunjuk dari j
dan k
secara bertahap akan lebih dekat satu sama lain. Akhirnya mereka akan saling melewati, dan pada saat itu kita tidak perlu mencoba hal lain untuk itu i
, karena kita akan menjumlahkan elemen yang sama, hanya dalam urutan yang berbeda. Setelah itu, kami mencoba yang berikutnya i
dan ulangi.
Akhirnya, kita akan menghabiskan kemungkinan yang berguna, atau kita akan menemukan solusinya. Anda dapat melihat bahwa ini adalah O (n 2 ) karena kami mengeksekusi loop luar O (n) kali dan kami mengeksekusi loop dalam O (n) kali. Dimungkinkan untuk melakukan ini secara sub-kuadrat jika Anda benar-benar suka, dengan mewakili setiap integer sebagai vektor bit dan melakukan transformasi Fourier cepat, tetapi itu di luar cakupan jawaban ini.
Catatan: Karena ini adalah pertanyaan wawancara, saya telah sedikit menipu: algoritma ini memungkinkan pemilihan elemen yang sama beberapa kali. Artinya, (-1, -1, 2) akan menjadi solusi yang valid, seperti halnya (0, 0, 0). Ia juga hanya menemukan jawaban yang tepat , bukan jawaban yang paling dekat, seperti judulnya. Sebagai latihan untuk pembaca, saya akan membiarkan Anda mencari tahu cara membuatnya bekerja dengan elemen yang berbeda saja (tapi itu perubahan yang sangat sederhana) dan jawaban yang tepat (yang juga merupakan perubahan yang sederhana).