Saya memiliki pengalaman wawancara kerja yang menarik beberapa waktu lalu. Pertanyaannya dimulai dengan sangat mudah:
Q1 : Kami memiliki tas berisi nomor
1
,2
,3
, ...,100
. Setiap angka muncul tepat sekali, sehingga ada 100 angka. Sekarang satu nomor dipilih secara acak dari tas. Temukan nomor yang hilang.
Saya telah mendengar pertanyaan wawancara ini sebelumnya, tentu saja, jadi saya dengan sangat cepat menjawab di sepanjang baris:
A1 : Nah, jumlah dari angka-angka
1 + 2 + 3 + … + N
itu(N+1)(N/2)
(lihat Wikipedia: jumlah deret aritmatika ). SebabN = 100
, jumlahnya adalah5050
.Jadi, jika semua angka ada di dalam tas, jumlahnya akan tepat
5050
. Karena satu nomor hilang, jumlahnya akan kurang dari ini, dan perbedaannya adalah angka itu. Jadi kita dapat menemukan nomor yang hilang dalam ruangO(N)
dan waktuO(1)
.
Pada titik ini saya pikir saya telah melakukannya dengan baik, tetapi tiba-tiba pertanyaan itu berubah secara tak terduga:
T2 : Itu benar, tapi sekarang bagaimana Anda melakukan ini jika DUA nomor tidak ada?
Saya belum pernah melihat / mendengar / mempertimbangkan variasi ini sebelumnya, jadi saya panik dan tidak bisa menjawab pertanyaan. Pewawancara bersikeras mengetahui proses pemikiran saya, jadi saya menyebutkan bahwa mungkin kita bisa mendapatkan lebih banyak informasi dengan membandingkan produk yang diharapkan, atau mungkin melakukan pass kedua setelah mengumpulkan beberapa informasi dari pass pertama, dll, tetapi saya benar-benar hanya menembak dalam gelap daripada benar-benar memiliki jalan yang jelas ke solusinya.
Pewawancara memang mencoba mendorong saya dengan mengatakan bahwa memiliki persamaan kedua memang merupakan salah satu cara untuk menyelesaikan masalah. Pada titik ini saya agak kesal (karena tidak mengetahui jawaban sebelumnya), dan bertanya apakah ini adalah teknik pemrograman umum (baca: "berguna"), atau apakah itu hanya trik / jawaban gotcha.
Jawaban pewawancara mengejutkan saya: Anda dapat menggeneralisasi teknik untuk menemukan 3 angka yang hilang. Bahkan, Anda dapat menggeneralisasikannya untuk menemukan k nomor yang hilang.
Qk : Jika nomor k benar -benar hilang dari tas, bagaimana Anda menemukannya secara efisien?
Ini beberapa bulan yang lalu, dan saya masih tidak tahu apa teknik ini. Jelas ada Ω(N)
waktu batas bawah karena kita harus memindai semua angka setidaknya sekali, tetapi pewawancara bersikeras bahwa WAKTU dan SPACE kompleksitas teknik pemecahan (minus O(N)
waktu scan input) didefinisikan dalam k tidak N .
Jadi pertanyaannya di sini sederhana:
- Bagaimana Anda memecahkan Q2 ?
- Bagaimana Anda memecahkan Q3 ?
- Bagaimana Anda memecahkan Qk ?
Klarifikasi
- Secara umum ada N angka dari 1 .. N , bukan hanya 1..100.
- Saya tidak mencari solusi berbasis set yang jelas, misalnya menggunakan bit set , mengkodekan ada / tidaknya setiap angka dengan nilai bit yang ditunjuk, oleh karena itu menggunakan
O(N)
bit dalam ruang tambahan. Kita tidak bisa setiap ruang tambahan sebanding dengan N . - Saya juga tidak mencari pendekatan sort-first yang jelas. Ini dan pendekatan berbasis set layak disebutkan dalam wawancara (mudah diimplementasikan, dan tergantung pada N , bisa sangat praktis). Saya mencari solusi Holy Grail (yang mungkin atau mungkin tidak praktis untuk diterapkan, tetapi memiliki karakteristik asimptotik yang diinginkan).
Jadi sekali lagi, tentu saja Anda harus memindai input O(N)
, tetapi Anda hanya dapat menangkap sejumlah kecil informasi (didefinisikan sebagai k bukan N ), dan kemudian harus menemukan k angka yang hilang entah bagaimana.
XOR
semua angka dari1
hinggan
, kemudian memberikan hasil dengan semua angka dalam array yang diberikan. Pada akhirnya Anda memiliki nomor yang hilang. Dalam solusi ini Anda tidak perlu peduli dengan overflow seperti dalam menyimpulkan.Jawaban:
Berikut ringkasan tautan Dimitris Andreou .
Ingat jumlah kekuatan ke-i, di mana i = 1,2, .., k. Ini mengurangi masalah untuk menyelesaikan sistem persamaan
a 1 + a 2 + ... + a k = b 1
a 1 2 + a 2 2 + ... + a k 2 = b 2
...
a 1 k + a 2 k + ... + a k k = b k
Menggunakan identitas Newton , mengetahui b i memungkinkan untuk menghitung
c 1 = a 1 + a 2 + ... a k
c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k
...
c k = a 1 a 2 ... a k
Jika Anda memperluas polinomial (xa 1 ) ... (xa k ) koefisiennya akan persis c 1 , ..., c k - lihat formula Viète . Karena setiap faktor polinomial unik (ring polinomial adalah domain Euclidean ), ini berarti i ditentukan secara unik, hingga permutasi.
Ini mengakhiri bukti bahwa daya ingat sudah cukup untuk memulihkan angka. Untuk konstanta k, ini adalah pendekatan yang baik.
Namun, ketika k bervariasi, pendekatan langsung dari komputasi c 1 , ..., ck sangat mahal, karena ck adalah produk dari semua angka yang hilang, besarnya n! / (Nk) !. Untuk mengatasinya, lakukan perhitungan dalam bidang Zq , di mana q adalah bilangan prima sehingga n <= q <2n - itu ada oleh postulat Bertrand . Buktinya tidak perlu diubah, karena rumus masih berlaku, dan faktorisasi polinomial masih unik. Anda juga memerlukan algoritme untuk faktorisasi bidang terbatas, misalnya yang dilakukan oleh Berlekamp atau Cantor-Zassenhaus .
Pseudocode tingkat tinggi untuk k konstan:
Untuk memvariasikan k, temukan bilangan prima n <= q <2n menggunakan misal Miller-Rabin, dan lakukan langkah-langkah dengan semua bilangan dikurangi modulo q.
EDIT: Versi sebelumnya dari jawaban ini menyatakan bahwa alih-alih Z q , di mana q adalah prima, dimungkinkan untuk menggunakan bidang hingga dengan karakteristik 2 (q = 2 ^ (log n)). Ini tidak terjadi, karena rumus Newton memerlukan pembagian dengan angka hingga k.
sumber
q = 2^(log n)
. (Bagaimana Anda membuat super dan subskrip?!)O(N^2)
solusi paling sepele mungkin akan mengungguli keindahan ini bahkan untuk yang cukup tinggiN
. Buat saya berpikir tentang ini: tinyurl.com/c8fwgw Meskipun demikian, kerja bagus! Saya tidak akan memiliki kesabaran untuk merangkak melalui semua matematika :)hash set
dan mengulangi1...N
suite menggunakan pencarian untuk menentukan apakah nomor hilang, akan menjadi yang paling umum, tercepat dalamk
variasi rata-rata , solusi yang paling dapat dipertahankan yang paling dapat dipertahankan dan dapat dipahami. Tentu saja cara matematika itu mengesankan tetapi di suatu tempat di sepanjang jalan Anda perlu menjadi insinyur dan bukan ahli matematika. Terutama ketika bisnis terlibat.Anda akan menemukannya dengan membaca beberapa halaman Muthukrishnan - Algoritma Aliran Data: Teka-teki 1: Menemukan Angka yang Hilang . Ini menunjukkan persis generalisasi yang Anda cari . Mungkin inilah yang pewawancara Anda baca dan mengapa ia mengajukan pertanyaan-pertanyaan ini.
Sekarang, jika hanya orang yang akan mulai menghapus jawaban yang dimasukkan atau diganti oleh perlakuan Muthukrishnan, dan membuat teks ini lebih mudah ditemukan. :)
Lihat juga jawaban terkait langsung sdcvvc , yang juga termasuk pseudocode (hore! Tidak perlu membaca formulasi matematika yang rumit :)) (terima kasih, kerja bagus!).
sumber
Kita dapat memecahkan Q2 dengan menjumlahkan kedua angka itu sendiri, dan kuadrat dari angka tersebut.
Kami kemudian dapat mengurangi masalah menjadi
Di mana
x
dany
seberapa jauh jumlahnya di bawah nilai yang diharapkan.Pengganti memberi kita:
Yang kemudian bisa kita pecahkan untuk menentukan nomor kita yang hilang.
sumber
Seperti yang ditunjukkan oleh @j_random_hacker, ini sangat mirip dengan Menemukan duplikat dalam ruang O (n) dan O (1) , dan adaptasi jawaban saya di sana juga berfungsi di sini.
Dengan asumsi bahwa "tas" diwakili oleh array
A[]
ukuran 1 berbasisN - k
, kita dapat menyelesaikan Qk dalamO(N)
waktu danO(k)
ruang tambahan.Pertama, kita perluas array kita
A[]
dengank
elemen, sehingga ukurannya sekarangN
. Ini adalahO(k)
ruang tambahan. Kami kemudian menjalankan algoritma kode semu berikut:Loop pertama menginisialisasi
k
entri tambahan ke sama dengan entri pertama dalam array (ini hanya nilai nyaman yang kita tahu sudah ada dalam array - setelah langkah ini, setiap entri yang hilang dalam array ukuran awalN-k
adalah masih hilang dalam array yang diperluas).Loop kedua memungkinkan array diperluas sehingga jika elemen
x
hadir setidaknya satu kali, maka salah satu entri tersebut akan berada pada posisiA[x]
.Perhatikan bahwa meskipun memiliki loop bersarang, ia masih berjalan dalam
O(N)
waktu - swap hanya terjadi jika adai
sedemikian rupaA[i] != i
, dan setiap swap menetapkan setidaknya satu elemen sedemikian rupaA[i] == i
, di mana itu tidak benar sebelumnya. Ini berarti bahwa jumlah total swap (dan dengan demikian jumlah total eksekusi dariwhile
loop body) paling banyakN-1
.Loop ketiga mencetak indeks-indeks array
i
yang tidak ditempati oleh nilaii
- ini berarti yangi
pasti telah hilang.sumber
A[i]
, yang berarti bahwa iterasi berikutnya tidak akan membandingkan dua nilai yang sama dengan yang sebelumnya. Yang baruA[i]
akan sama dengan loop terakhirA[A[i]]
, tetapi yang baruA[A[i]]
akan menjadi nilai baru . Cobalah dan lihatlah.Saya meminta seorang anak berusia 4 tahun untuk menyelesaikan masalah ini. Dia mengurutkan angka dan kemudian menghitung. Ini memiliki persyaratan ruang O (lantai dapur), dan itu bekerja dengan mudah namun banyak bola yang hilang.
sumber
Tidak yakin, apakah ini solusi yang paling efisien, tetapi saya akan mengulang semua entri, dan menggunakan bitet untuk mengingat, angka mana yang ditetapkan, dan kemudian menguji 0 bit.
Saya suka solusi sederhana - dan saya bahkan percaya, bahwa mungkin lebih cepat daripada menghitung jumlah, atau jumlah kotak, dll.
sumber
O(N)
jenis penghitungan maupun jenisO(N log N)
perbandingan bukanlah yang saya cari, meskipun keduanya merupakan solusi yang sangat sederhana.Saya belum memeriksa matematika, tetapi saya menduga bahwa menghitung
Σ(n^2)
dalam kode yang sama seperti yang kita hitungΣ(n)
akan memberikan informasi yang cukup untuk mendapatkan dua angka yang hilang, LakukanΣ(n^3)
juga jika ada tiga, dan seterusnya.sumber
Masalah dengan solusi berdasarkan jumlah angka adalah mereka tidak memperhitungkan biaya penyimpanan dan bekerja dengan angka dengan eksponen besar ... dalam praktiknya, agar dapat bekerja untuk n yang sangat besar, perpustakaan angka besar akan digunakan . Kita dapat menganalisis pemanfaatan ruang untuk algoritma ini.
Kita dapat menganalisis kompleksitas ruang dan waktu dari algoritma sdcvvc dan Dimitris Andreou.
Penyimpanan:
Begitu
l_j \in \Theta(j log n)
Total penyimpanan yang digunakan:
\sum_{j=1}^k l_j \in \Theta(k^2 log n)
Ruang yang digunakan: dengan asumsi bahwa komputasi
a^j
membutuhkanceil(log_2 j)
waktu, total waktu:Total waktu yang digunakan:
\Theta(kn log n)
Jika waktu dan ruang ini memuaskan, Anda dapat menggunakan algoritma rekursif sederhana. Biarkan b! I menjadi entri ke-i di tas, n jumlah angka sebelum pemindahan, dan k jumlah pemindahan. Dalam sintaksis Haskell ...
Penyimpanan yang digunakan:
O(k)
untuk daftar,O(log(n))
untuk tumpukan:O(k + log(n))
Algoritma ini lebih intuitif, memiliki kompleksitas waktu yang sama, dan menggunakan lebih sedikit ruang.sumber
isInRange
adalah O (log n) , bukan O (1) : ia membandingkan angka dalam rentang 1..n, sehingga harus membandingkan bit O (log n) . Saya tidak tahu sampai sejauh mana kesalahan ini mempengaruhi sisa analisis.Tunggu sebentar. Seperti yang dinyatakan pertanyaan, ada 100 nomor di dalam tas. Tidak peduli seberapa besar k, masalah dapat diselesaikan dalam waktu yang konstan karena Anda dapat menggunakan satu set dan menghapus angka dari set di paling banyak 100 - iterasi loop. 100 konstan. Himpunan angka yang tersisa adalah jawaban Anda.
Jika kita menggeneralisasi solusi ke angka dari 1 ke N, tidak ada yang berubah kecuali N bukan konstanta, jadi kita berada dalam O (N - k) = O (N) waktu. Misalnya, jika kita menggunakan set bit, kita mengatur bit ke 1 dalam waktu O (N), beralih melalui angka, mengatur bit ke 0 saat kita pergi (O (Nk) = O (N)) dan kemudian kita punya jawabannya.
Tampak bagi saya bahwa pewawancara bertanya kepada Anda bagaimana mencetak isi set terakhir dalam waktu O (k) daripada waktu O (N). Jelas, dengan set bit, Anda harus mengulangi semua N bit untuk menentukan apakah Anda harus mencetak nomor atau tidak. Namun, jika Anda mengubah cara set diimplementasikan, Anda dapat mencetak angka dalam iterasi. Ini dilakukan dengan memasukkan angka-angka ke dalam objek yang akan disimpan dalam hash set dan daftar tertaut ganda. Saat Anda menghapus objek dari kumpulan hash, Anda juga menghapusnya dari daftar. Jawabannya akan ditinggalkan dalam daftar yang panjangnya sekarang k.
sumber
Untuk memecahkan 2 (dan 3) pertanyaan angka yang hilang, Anda dapat memodifikasi
quickselect
, yang rata-rata berjalanO(n)
dan menggunakan memori konstan jika partisi dilakukan di tempat.Partisi himpunan sehubungan dengan pivot acak
p
ke dalam partisil
, yang berisi angka lebih kecil dari pivot, danr
, yang berisi angka lebih besar dari pivot.Tentukan di partisi mana 2 angka yang hilang berada dengan membandingkan nilai pivot dengan ukuran setiap partisi (
p - 1 - count(l) = count of missing numbers in l
dann - count(r) - p = count of missing numbers in r
)a) Jika setiap partisi kehilangan satu nomor, gunakan pendekatan perbedaan jumlah untuk menemukan setiap nomor yang hilang.
(1 + 2 + ... + (p-1)) - sum(l) = missing #1
dan((p+1) + (p+2) ... + n) - sum(r) = missing #2
b) Jika satu partisi kehilangan kedua angka dan partisi kosong, maka angka yang hilang adalah salah satu
(p-1,p-2)
atau(p+1,p+2)
tergantung pada partisi mana yang kehilangan angka.Jika satu partisi kehilangan 2 angka tetapi tidak kosong, maka kembalilah ke partiton itu.
Dengan hanya 2 angka yang hilang, algoritme ini selalu membuang setidaknya satu partisi, sehingga mempertahankan
O(n)
kompleksitas waktu rata-rata pemilihan cepat. Demikian pula, dengan 3 angka yang hilang, algoritma ini juga membuang setidaknya satu partisi dengan setiap pass (karena seperti dengan 2 angka yang hilang, paling banyak hanya 1 partisi yang akan mengandung beberapa angka yang hilang). Namun, saya tidak yakin seberapa banyak kinerja menurun ketika lebih banyak angka yang hilang ditambahkan.Berikut ini adalah implementasi yang tidak menggunakan partisi di tempat, jadi contoh ini tidak memenuhi persyaratan ruang tetapi menggambarkan langkah-langkah algoritma:
Demo
sumber
Inilah solusi yang menggunakan k bit penyimpanan ekstra, tanpa trik pintar dan langsung. Waktu eksekusi O (n), ruang ekstra O (k). Hanya untuk membuktikan bahwa ini dapat diselesaikan tanpa membaca solusinya terlebih dahulu atau menjadi jenius:
sumber
(data [n - 1 - odd] % 2 == 1) ++odd;
?Bisakah Anda memeriksa apakah setiap nomor ada? Jika ya, Anda dapat mencoba ini:
jika nomor yang hilang adalah
x
dany
kemudian:Jadi, Anda memeriksa rentang dari
1
kemax(x)
dan menemukan nomornyasumber
max(x)
artinya, kapanx
angka itu?Mungkin algoritma ini dapat berfungsi untuk pertanyaan 1:
Atau lebih baik lagi:
Algoritma ini sebenarnya dapat diperluas untuk dua nomor yang hilang. Langkah pertama tetap sama. Ketika kita memanggil GetValue dengan dua angka yang hilang, hasilnya
a1^a2
adalah dua angka yang hilang. Katakanlahval = a1^a2
Sekarang untuk menyaring a1 dan a2 dari val kita mengambil bit yang telah ditentukan di val. Katakanlah
ith
bit diatur dalam val. Itu berarti a1 dan a2 memiliki paritas yang berbeda padaith
posisi bit. Sekarang kita lakukan iterasi lain pada array asli dan menyimpan dua nilai xor. Satu untuk angka-angka yang memiliki bit set ith dan lainnya yang tidak memiliki bit set ith. Kami sekarang memiliki dua ember angka, dan dijamin bahwa itua1 and a2
akan terletak pada ember yang berbeda. Sekarang ulangi hal yang sama dengan yang kami lakukan untuk menemukan satu elemen yang hilang pada masing-masing ember.sumber
k=1
, kan? Tapi saya suka menggunakanxor
jumlah berlebih, sepertinya sedikit lebih cepat.Anda dapat memecahkan Q2 jika Anda memiliki jumlah dari kedua daftar dan produk dari kedua daftar.
(l1 adalah yang asli, l2 adalah daftar yang dimodifikasi)
Kami dapat mengoptimalkan ini karena jumlah dari rangkaian aritmatika adalah n kali rata-rata istilah pertama dan terakhir:
Sekarang kita tahu bahwa (jika a dan b adalah angka yang dihapus):
Jadi kita dapat mengatur ulang ke:
Dan gandakan:
Dan mengatur ulang sehingga sisi kanan adalah nol:
Maka kita bisa menyelesaikannya dengan rumus kuadratik:
Contoh kode Python 3:
Saya tidak tahu kerumitan fungsi sqrt, pengurangan dan penjumlahan jadi saya tidak dapat menghitung kompleksitas solusi ini (jika ada yang tahu, silakan komentar di bawah.)
sumber
x1*x2*x3*...
?Untuk Q2 ini adalah solusi yang sedikit lebih tidak efisien daripada yang lain, tetapi masih memiliki runtime O (N) dan membutuhkan ruang O (k).
Idenya adalah menjalankan algoritma asli dua kali. Di yang pertama Anda mendapatkan jumlah total yang hilang, yang memberi Anda batas atas dari angka yang hilang. Sebut saja nomor ini
N
. Anda tahu bahwa dua angka yang hilang akan dijumlahkanN
, jadi angka pertama hanya bisa berada dalam interval[1, floor((N-1)/2)]
sementara yang kedua akan masuk[floor(N/2)+1,N-1]
.Dengan demikian Anda mengulang semua angka sekali lagi, membuang semua angka yang tidak termasuk dalam interval pertama. Yang ada, Anda melacak jumlah mereka. Akhirnya, Anda akan tahu salah satu dari dua angka yang hilang, dan dengan ekstensi yang kedua.
Saya merasa bahwa metode ini dapat digeneralisasi dan mungkin beberapa pencarian berjalan secara "paralel" selama satu kali masukan, tetapi saya belum menemukan caranya.
sumber
Saya pikir ini dapat dilakukan tanpa persamaan dan teori matematika yang kompleks. Di bawah ini adalah proposal untuk solusi kompleksitas tempat dan O (2n):
Asumsi formulir input:
# angka dalam tas = n
# angka yang hilang = k
Angka-angka dalam tas diwakili oleh array panjang
Panjang array input untuk algo = n
Entri yang hilang dalam array (angka yang dikeluarkan dari kantong) diganti dengan nilai elemen pertama dalam array.
Misalnya. Awalnya tas terlihat seperti [2,9,3,7,8,6,4,5,1,10]. Jika 4 dikeluarkan, nilai 4 akan menjadi 2 (elemen pertama dari array). Karenanya setelah mengeluarkan 4 tas akan terlihat seperti [2,9,3,7,8,6,2,5,1,10]
Kunci dari solusi ini adalah untuk menandai INDEX dari nomor yang dikunjungi dengan meniadakan nilai pada INDEX itu ketika array dilintasi.
sumber
Ada cara umum untuk menggeneralisasi algoritma streaming seperti ini. Idenya adalah untuk menggunakan sedikit pengacakan untuk semoga 'menyebar'
k
elemen ke dalam sub masalah independen, di mana algoritma asli kami memecahkan masalah bagi kami. Teknik ini digunakan dalam rekonstruksi sinyal jarang, antara lain.a
ukuranu = k^2
.h : {1,...,n} -> {1,...,u}
. (Seperti multiply-shift )i
di1, ..., n
peningkatana[h(i)] += i
x
dalam aliran input, pengurangana[h(x)] -= x
.Jika semua angka yang hilang telah di hash ke kotak berbeda, elemen bukan nol dari array sekarang akan berisi angka yang hilang.
Probabilitas bahwa pasangan tertentu dikirim ke keranjang yang sama, kurang dari
1/u
definisi fungsi hash universal. Karena ada tentangk^2/2
pasangan, kami memiliki probabilitas kesalahan paling banyakk^2/2/u=1/2
. Artinya, kami berhasil dengan probabilitas setidaknya 50%, dan jika kami meningkatkanu
kami meningkatkan peluang kami.Perhatikan bahwa algoritma ini membutuhkan
k^2 logn
bit ruang (Kita perlulogn
bit per array ember.) Ini cocok dengan ruang yang diperlukan oleh jawaban @ Dimitris Andreou (Khususnya persyaratan ruang faktorisasi polinomial, yang kebetulan juga diacak.) Algoritma ini juga memiliki konstanta waktu per pembaruan, bukan waktuk
dalam hal jumlah daya.Bahkan, kita bisa lebih efisien daripada metode jumlah daya dengan menggunakan trik yang dijelaskan dalam komentar.
sumber
xor
di setiap bucket, bukansum
, jika itu lebih cepat di mesin kami.k <= sqrt(n)
- setidaknya jikau=k^2
? Misalkan k = 11 dan n = 100, maka Anda akan memiliki 121 ember dan algoritmanya akan menjadi mirip dengan memiliki array 100 bit yang Anda centang saat Anda membaca masing-masing # dari sungai. Meningkatkanu
meningkatkan peluang keberhasilan tetapi ada batas seberapa banyak Anda dapat meningkatkannya sebelum Anda melampaui batasan ruang.n
jauh lebih besar daripadak
, saya pikir, tetapi Anda benar-benar bisa mendapatkan ruang untukk logn
dengan metode yang sangat mirip dengan hashing yang dijelaskan, sambil tetap memiliki pembaruan waktu yang konstan. Ini dijelaskan di gnunet.org/eppstein-set- rekonsiliasi , seperti metode jumlah kekuatan, tetapi pada dasarnya Anda hash untuk 'dua k' ember dengan fungsi hash yang kuat seperti tabulasi hashing, yang menjamin bahwa beberapa ember hanya akan memiliki satu elemen . Untuk memecahkan kode, Anda mengidentifikasi ember itu dan menghilangkan elemen dari kedua embernya, yang (kemungkinan) membebaskan ember lain dan seterusnyaSolusi yang sangat sederhana untuk Q2 yang saya terkejut tidak ada yang menjawab. Gunakan metode dari Q1 untuk menemukan jumlah dari dua angka yang hilang. Mari kita sebutkan dengan S, maka salah satu angka yang hilang lebih kecil dari S / 2 dan yang lainnya lebih besar dari S / 2 (duh). Jumlahkan semua angka dari 1 hingga S / 2 dan bandingkan dengan hasil rumus (mirip dengan metode pada Q1) untuk menemukan yang lebih rendah di antara angka yang hilang. Kurangi dari S untuk menemukan angka hilang yang lebih besar.
sumber
Masalah yang sangat bagus Saya akan menggunakan perbedaan set untuk Qk. Banyak bahasa pemrograman bahkan memiliki dukungan untuk itu, seperti di Ruby:
Ini mungkin bukan solusi yang paling efisien tapi itu salah satu yang akan saya gunakan dalam kehidupan nyata jika saya dihadapkan dengan tugas seperti itu dalam kasus ini (batas yang diketahui, batas rendah). Jika himpunan bilangan akan sangat besar maka saya akan mempertimbangkan algoritma yang lebih efisien, tentu saja, tetapi sampai saat itu solusi sederhana akan cukup bagi saya.
sumber
Anda dapat mencoba menggunakan Bloom Filter . Masukkan setiap nomor dalam kantong ke dalam mekar, lalu ulangi set lengkap 1-k sampai melaporkan masing-masing yang tidak ditemukan. Ini mungkin tidak menemukan jawabannya di semua skenario, tetapi mungkin merupakan solusi yang cukup baik.
sumber
Saya akan mengambil pendekatan berbeda untuk pertanyaan itu dan menyelidiki pewawancara untuk rincian lebih lanjut tentang masalah yang lebih besar yang dia coba selesaikan. Bergantung pada masalah dan persyaratan yang mengelilinginya, solusi berbasis set yang jelas mungkin adalah hal yang benar dan pendekatan menghasilkan-daftar-dan-memilih-melalui-itu-sesudahnya mungkin tidak.
Sebagai contoh, mungkin pewawancara akan mengirim
n
pesan dan perlu tahuk
yang tidak menghasilkan balasan dan perlu mengetahuinya sesedikit mungkin jam dinding setelahn-k
balasan th tiba. Katakan juga bahwa sifat saluran pesan sedemikian rupa sehingga bahkan berjalan dengan kecepatan penuh, ada cukup waktu untuk melakukan beberapa pemrosesan antar pesan tanpa berdampak pada berapa lama waktu yang diperlukan untuk menghasilkan hasil akhir setelah balasan terakhir tiba. Waktu itu dapat digunakan untuk memasukkan beberapa aspek identifikasi dari setiap pesan yang dikirim ke set dan menghapusnya ketika setiap balasan yang sesuai tiba. Setelah balasan terakhir tiba, satu-satunya hal yang harus dilakukan adalah menghapus pengenalnya dari set, yang biasanya membutuhkan implementasiO(log k+1)
. Setelah itu, set berisi daftark
elemen yang hilang dan tidak ada proses tambahan yang harus dilakukan.Ini tentu bukan pendekatan tercepat untuk pemrosesan batch jumlah tas yang dibuat sebelumnya karena semuanya berjalan
O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))
. Tapi itu bekerja untuk nilai apa punk
(bahkan jika itu tidak diketahui sebelumnya) dan dalam contoh di atas diterapkan dengan cara yang meminimalkan interval paling kritis.sumber
Anda dapat memotivasi solusi dengan memikirkannya dalam hal simetri (kelompok, dalam bahasa matematika). Tidak peduli urutan himpunan angka, jawabannya harus sama. Jika Anda akan menggunakan
k
fungsi untuk membantu menentukan elemen yang hilang, Anda harus memikirkan fungsi apa yang memiliki properti itu: simetris. Fungsis_1(x) = x_1 + x_2 + ... + x_n
ini adalah contoh dari fungsi simetris, tetapi ada yang lain dari tingkat yang lebih tinggi. Secara khusus, perhatikan fungsi simetris dasar . Fungsi simetris dasar derajat 2 adalahs_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n
, jumlah dari semua produk dari dua elemen. Demikian pula untuk fungsi simetris dasar derajat 3 dan lebih tinggi. Mereka jelas simetris. Selanjutnya, ternyata mereka adalah blok bangunan untuk semua fungsi simetris.Anda dapat membangun fungsi simetris dasar saat Anda memerhatikannya
s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))
. Pemikiran lebih lanjut harus meyakinkan Anda itus_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))
dan seterusnya, sehingga mereka dapat dihitung dalam satu pass.Bagaimana cara mengetahui item mana yang hilang dari array? Pikirkan tentang jumlahnya banyak
(z-x_1)(z-x_2)...(z-x_n)
. Ini mengevaluasi0
jika Anda memasukkan salah satu nomorx_i
. Memperluas jumlahnya banyak, Anda dapatkanz^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n
. Fungsi simetris dasar juga muncul di sini, yang benar-benar tidak mengejutkan, karena polinom harus tetap sama jika kita menerapkan permutasi ke akar.Jadi kita dapat membangun polinomial dan mencoba memfaktorkannya untuk mencari tahu angka mana yang tidak ada dalam set, seperti yang telah disebutkan orang lain.
Akhirnya, jika kita khawatir tentang meluap memori dengan jumlah besar (polinomial simetris ke-n adalah urutan
100!
), kita dapat melakukan perhitungan ini dimod p
manap
bilangan prima lebih besar dari 100. Dalam hal ini kita mengevaluasi polinomialmod p
dan menemukan bahwa itu mengevaluasi lagi untuk0
saat input adalah angka di set, dan mengevaluasi ke nilai non-nol ketika input adalah angka tidak di set. Namun, seperti yang telah ditunjukkan orang lain, untuk mendapatkan nilai dari polinomial dalam waktu yang tergantungk
, tidakN
, kita harus memperhitungkan polinomialmod p
.sumber
Namun cara lain adalah menggunakan penyaringan grafik residual.
Misalkan kita memiliki angka 1 hingga 4 dan 3 tidak ada. Representasi biner adalah sebagai berikut,
1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b
Dan saya bisa membuat diagram alur seperti berikut ini.
Perhatikan bahwa grafik aliran berisi x node, sementara x menjadi jumlah bit. Dan jumlah tepi maksimum adalah (2 * x) -2.
Jadi untuk integer 32 bit akan membutuhkan ruang O (32) atau O (1).
Sekarang jika saya menghapus kapasitas untuk masing-masing angka mulai dari 1,2,4 maka saya dibiarkan dengan grafik sisa.
Akhirnya saya akan menjalankan loop seperti berikut ini,
Sekarang hasilnya adalah
result
berisi angka-angka yang tidak hilang juga (false positive). Tetapi k <= (ukuran hasilnya) <= n ketika adak
elemen yang hilang.Saya akan memeriksa daftar yang diberikan untuk terakhir kali untuk menandai hasil yang hilang atau tidak.
Jadi kompleksitas waktu adalah O (n).
Akhirnya, adalah mungkin untuk mengurangi jumlah positif palsu (dan ruang yang dibutuhkan) dengan mengambil node
00
,01
,11
,10
bukan hanya0
dan1
.sumber
Anda mungkin perlu klarifikasi tentang arti O (k).
Ini solusi sepele untuk arbitrary k: untuk setiap v dalam set angka Anda, kumpulkan jumlah 2 ^ v. Pada akhirnya, loop i dari 1 ke N. Jika jumlah bitwise ANDed dengan 2 ^ i adalah nol, maka saya hilang. (Atau secara numerik, jika jumlah lantai dibagi 2 ^ i adalah genap. Atau
sum modulo 2^(i+1)) < 2^i
.)Mudah kan? O (N) waktu, O (1) penyimpanan, dan mendukung sewenang-wenang k.
Kecuali bahwa Anda menghitung angka besar yang pada komputer nyata masing-masing akan membutuhkan ruang O (N). Bahkan, solusi ini identik dengan vektor sedikit.
Jadi Anda bisa menjadi pintar dan menghitung jumlah dan jumlah kotak dan jumlah kubus ... hingga jumlah v ^ k, dan melakukan matematika mewah untuk mengekstraksi hasilnya. Tetapi itu juga angka besar, yang menimbulkan pertanyaan: model operasi abstrak apa yang sedang kita bicarakan? Seberapa cocok dalam O (1) ruang, dan berapa lama waktu yang dibutuhkan untuk merangkum jumlah ukuran apa pun yang Anda butuhkan?
sumber
Ini adalah solusi yang tidak bergantung pada matematika kompleks seperti jawaban sdcvvc / Dimitris Andreou, tidak mengubah array input seperti yang dilakukan caf dan Kolonel Panic, dan tidak menggunakan bitset ukuran besar seperti Chris Lercher, JeremyP dan banyak yang melakukannya. Pada dasarnya, saya mulai dengan ide Svalorzen / Gilad Deutch untuk Q2, menggeneralisasikannya ke kasus umum Qk dan diimplementasikan di Jawa untuk membuktikan bahwa algoritme berfungsi.
Ide
Misalkan kita memiliki interval I arbitrer yang kita hanya tahu bahwa itu mengandung setidaknya satu dari angka yang hilang. Setelah satu lulus melalui array masukan, hanya melihat angka-angka dari saya , kita dapat memperoleh baik jumlah S dan kuantitas Q hilang nomor dari saya . Kami melakukan ini dengan hanya decrementing I panjang setiap kali kita jumpai nomor dari saya (untuk memperoleh Q ) dan dengan mengurangi pra-dihitung jumlah semua nomor di I dengan nomor ditemui setiap kali (untuk memperoleh S ).
Sekarang kita melihat S dan Q . Jika Q = 1 , itu berarti bahwa kemudian saya hanya berisi salah satu nomor yang hilang, dan jumlah ini jelas S . Kami menandai saya sebagai selesai (disebut "tidak ambigu" dalam program ini) dan meninggalkannya dari pertimbangan lebih lanjut. Di sisi lain, jika Q> 1 , kita dapat menghitung rata-rata A = S / Q nomor hilang terkandung dalam saya . Seperti semua nomor yang berbeda, setidaknya satu dari nomor tersebut secara ketat kurang dari A dan setidaknya satu ketat lebih besar dari A . Sekarang kita membagi saya di Amenjadi dua interval yang lebih kecil yang masing-masing berisi setidaknya satu nomor yang hilang. Perhatikan bahwa tidak masalah ke interval mana yang kami tetapkan A jika itu adalah bilangan bulat.
Kami membuat larik array berikutnya menghitung S dan Q untuk masing-masing interval secara terpisah (tetapi dalam lintasan yang sama) dan setelah itu menandai interval dengan Q = 1 dan membagi interval dengan Q> 1 . Kami melanjutkan proses ini sampai tidak ada interval "ambigu" baru, yaitu kami tidak memiliki apa-apa untuk dibagi karena setiap interval mengandung tepat satu nomor yang hilang (dan kami selalu tahu nomor ini karena kami tahu S ). Kita mulai dari satu-satunya interval "seluruh rentang" yang berisi semua angka yang mungkin (seperti [1..N] dalam pertanyaan).
Analisis kompleksitas waktu dan ruang
Jumlah lintasan p yang perlu kita buat sampai proses berhenti tidak pernah lebih besar dari jumlah angka yang hilang k . Ketidaksetaraan p <= k dapat dibuktikan dengan ketat. Di sisi lain, ada juga batas atas empiris p <log 2 N + 3 yang berguna untuk nilai besar k . Kita perlu melakukan pencarian biner untuk setiap nomor array input untuk menentukan interval miliknya. Ini menambah pengali log k ke kompleksitas waktu.
Secara total, kompleksitas waktu adalah O (N ᛫ min (k, log N) ᛫ log k) . Perhatikan bahwa untuk k besar , ini jauh lebih baik daripada metode sdcvvc / Dimitris Andreou, yaitu O (N ᛫ k) .
Untuk pekerjaannya, algoritma ini membutuhkan O (k) ruang tambahan untuk menyimpan pada interval paling k , yang secara signifikan lebih baik daripada O (N) dalam solusi "bitset".
Implementasi Java
Berikut adalah kelas Java yang mengimplementasikan algoritma di atas. Selalu mengembalikan diurutkan array nomor hilang. Selain itu, tidak memerlukan angka yang hilang, hitung k karena menghitungnya pada pass pertama. Seluruh rentang angka diberikan oleh parameter
minNumber
danmaxNumber
(misalnya 1 dan 100 untuk contoh pertama dalam pertanyaan).Untuk keadilan, kelas ini menerima input dalam bentuk
NumberBag
objek.NumberBag
tidak mengizinkan modifikasi larik dan akses acak dan juga menghitung berapa kali larik diminta untuk melintasi sekuensial. Ini juga lebih cocok untuk pengujian array besar daripadaIterable<Integer>
karena menghindari tinjuint
nilai-nilai primitif dan memungkinkan membungkus sebagian besarint[]
untuk persiapan pengujian yang nyaman. Tidak sulit untuk mengganti, jika diinginkan,NumberBag
denganint[]
atauIterable<Integer>
mengetikkanfind
tanda tangan, dengan mengubah dua for-loop di dalamnya menjadi masing-masing.Tes
Contoh sederhana yang menunjukkan penggunaan kelas-kelas ini diberikan di bawah ini.
Pengujian array besar dapat dilakukan dengan cara ini:
Cobalah di Ideone
sumber
Saya yakin saya memiliki algoritma ruang
O(k)
dan waktuO(log(k))
, mengingat Anda memilikifloor(x)
danlog2(x)
fungsi untuk bilangan bulat besar yang tersedia:Anda memiliki
k
bilangan bulat panjang-bit (karena itulog8(k)
ruang) tempat Anda menambahkanx^2
, di mana x adalah angka berikutnya yang Anda temukan dalam tas:s=1^2+2^2+...
Ini membutuhkanO(N)
waktu (yang tidak menjadi masalah bagi pewawancara). Pada akhirnya Anda mendapatkanj=floor(log2(s))
yang merupakan jumlah terbesar yang Anda cari. Kemudians=s-j
dan Anda lakukan lagi di atas:Sekarang, Anda biasanya tidak memiliki fungsi lantai dan log2 untuk
2756
bilangan bulat-bit tetapi bukan untuk ganda. Begitu? Sederhananya, untuk setiap 2 byte (atau 1, atau 3, atau 4) Anda dapat menggunakan fungsi-fungsi ini untuk mendapatkan angka yang diinginkan, tetapi ini menambahO(N)
faktor kompleksitas waktusumber
Ini mungkin terdengar bodoh, tetapi, dalam masalah pertama yang disajikan kepada Anda, Anda harus melihat semua angka yang tersisa di tas untuk benar-benar menjumlahkannya untuk menemukan nomor yang hilang menggunakan persamaan itu.
Jadi, karena Anda bisa melihat semua angka, cari saja nomor yang hilang. Hal yang sama berlaku ketika dua nomor hilang. Cukup sederhana menurut saya. Tidak ada gunanya menggunakan persamaan ketika Anda bisa melihat angka yang tersisa di tas.
sumber
Saya pikir ini dapat digeneralisasi seperti ini:
Nyatakan S, M sebagai nilai awal untuk jumlah deret aritmatika dan perkalian.
Saya harus memikirkan rumus untuk menghitung ini, tetapi bukan itu intinya. Lagi pula, jika satu nomor tidak ada, Anda sudah memberikan solusinya. Namun, jika dua angka tidak ada, mari kita tunjukkan jumlah baru dan kelipatan total dengan S1 dan M1, yang akan menjadi sebagai berikut:
Karena Anda tahu S1, M1, M dan S, persamaan di atas dapat dipecahkan untuk menemukan a dan b, angka yang hilang.
Sekarang untuk tiga angka yang hilang:
Sekarang tidak diketahui Anda adalah 3 sementara Anda hanya memiliki dua persamaan yang dapat Anda pecahkan.
sumber
M1 = M / (a * b)
(lihat jawaban itu ). Maka itu berfungsi dengan baik.Saya tidak tahu apakah ini efisien atau tidak, tetapi saya ingin menyarankan solusi ini.
4. Dapatkan jumlah dari No yang hilang dengan pendekatan biasa Anda dari menjumlahkan rumus diff dan katakanlah diff adalah d.
Sekarang jalankan loop untuk mendapatkan pasangan yang memungkinkan (p, q) yang keduanya terletak pada [1, 100] dan jumlah ke d.
Ketika sepasang diperoleh, periksa apakah (hasil 3) XOR p = q dan jika ya kita selesai.
Harap perbaiki saya jika saya salah dan komentari kompleksitas waktu jika ini benar
sumber
Kita dapat melakukan Q1 dan Q2 di O (log n) sebagian besar waktu.
Misalkan kita
memory chip
terdiri dari arrayn
jumlahtest tubes
. Dan angkax
dalam tabung reaksi diwakili olehx
milliliter
cairan kimia.Misalkan prosesor kita adalah a
laser light
. Ketika kita menyalakan laser itu melintasi semua tabung tegak lurus dengan panjangnya. Setiap kali melewati cairan kimia, luminositas berkurang1
. Dan melewati cahaya pada tanda mililiter tertentu adalah operasiO(1)
.Sekarang jika kita menyalakan laser kita di tengah tabung reaksi dan mendapatkan hasil luminositas
n/2
.n/2
. Kami juga dapat memeriksa apakah luminositas berkurang oleh1
atau2
. jika dikurangi1
maka satu nomor yang hilang lebih kecil darin/2
dan yang lainnya lebih besar darin/2
. Jika dikurangi2
maka kedua angka lebih kecil darin/2
.Kami dapat mengulangi proses di atas berulang kali mempersempit domain masalah kami. Di setiap langkah, kami membuat domain lebih kecil setengahnya. Dan akhirnya kita bisa mencapai hasil kita.
Algoritma paralel yang layak disebut (karena mereka menarik),
O(log^3 n)
waktu. Dan kemudian nomor yang hilang dapat ditemukan oleh pencarian biner padaO(log n)
waktunya.n
prosesor maka setiap proses dapat memeriksa salah satu input dan mengatur beberapa flag yang mengidentifikasi nomor (dengan mudah dalam sebuah array). Dan pada langkah selanjutnya setiap proses dapat memeriksa setiap bendera dan akhirnya menampilkan nomor yang tidak ditandai. Seluruh proses akan memakanO(1)
waktu. Ini memilikiO(n)
kebutuhan ruang / memori tambahan .Perhatikan, bahwa dua algoritma paralel yang disediakan di atas mungkin memerlukan ruang tambahan seperti yang disebutkan dalam komentar .
sumber
O(logn)
di komputer.N
, dan lebih dariO(N)
waktu (dalam hal ketergantungan padaN
), yang kami ingin lakukan lebih baik daripada.