Pertanyaan wawancara menarik yang digunakan kolega saya:
Misalkan Anda diberikan daftar bilangan bulat 64-bit tak bertanda tangan yang sangat panjang dan tidak disortir. Bagaimana Anda menemukan bilangan bulat non-negatif terkecil yang tidak muncul dalam daftar?
TINDAK LANJUT: Sekarang solusi yang jelas dengan menyortir telah diusulkan, dapatkah Anda melakukannya lebih cepat daripada O (n log n)?
TINDAK LANJUT: Algoritme Anda harus berjalan di komputer dengan, katakanlah, memori 1GB
KLARIFIKASI: Daftar ini ada di RAM, meskipun mungkin menghabiskan banyak. Anda diberi ukuran daftarnya, katakanlah N, di muka.
Jawaban:
Jika struktur data dapat dimutasi pada tempatnya dan mendukung akses acak maka Anda dapat melakukannya dalam waktu O (N) dan O (1) ruang tambahan. Hanya melalui array secara berurutan dan untuk setiap indeks tulis nilai pada indeks ke indeks yang ditentukan oleh nilai, secara rekursif menempatkan nilai apa pun di lokasi itu ke tempatnya dan membuang nilai> N. Kemudian pergi lagi melalui array untuk mencari tempat di mana nilai tidak cocok dengan indeks - itu adalah nilai terkecil yang tidak ada dalam larik. Ini menghasilkan paling banyak perbandingan 3N dan hanya menggunakan beberapa nilai ruang sementara.
sumber
Berikut
O(N)
solusi sederhana yang menggunakanO(N)
ruang. Saya berasumsi bahwa kami membatasi daftar input ke bilangan non-negatif dan kami ingin mencari bilangan non-negatif pertama yang tidak ada dalam daftar.N
.N
boolean, diinisialisasi ke semuafalse
.X
dalam daftar, jikaX
kurang dariN
, setelX'th
elemen larik ketrue
.0
, cari elemen pertama yaitufalse
. Jika Anda menemukan yang pertamafalse
di indeksI
, makaI
itulah jawabannya. Sebaliknya (yaitu ketika semua elementrue
) jawabannya adalahN
.Dalam praktiknya, "array
N
boolean" mungkin akan dikodekan sebagai "bitmap" atau "bitset" yang direpresentasikan sebagai arraybyte
atauint
. Ini biasanya menggunakan lebih sedikit ruang (tergantung pada bahasa pemrograman) dan memungkinkan pemindaian untuk yang pertamafalse
dilakukan lebih cepat.Inilah bagaimana / mengapa algoritma bekerja.
Misalkan
N
angka dalam daftar tidak berbeda, atau salah satu atau lebih dari angka tersebut lebih besar dariN
. Artinya, setidaknya harus ada satu angka dalam rentang0 .. N - 1
yang tidak ada dalam daftar. Jadi masalah mencari bilangan hilang terkecil karenanya harus dikurangi menjadi masalah menemukan bilangan hilang terkecil kurang dariN
. Ini berarti kita tidak perlu melacak angka yang lebih besar atau sama denganN
... karena itu bukan jawabannya.Alternatif dari paragraf sebelumnya adalah bahwa list tersebut merupakan permutasi dari bilangan-bilangan tersebut
0 .. N - 1
. Dalam kasus ini, langkah 3 menetapkan semua elemen array ketrue
, dan langkah 4 memberi tahu kita bahwa nomor "hilang" pertama adalahN
.Kompleksitas komputasi algoritma ini
O(N)
dengan konstanta proporsionalitas yang relatif kecil. Itu membuat dua linier melewati daftar, atau hanya satu lulus jika panjang daftar diketahui untuk memulai. Tidak perlu mewakili seluruh daftar dalam memori, jadi penggunaan memori asimtotik algoritme adalah apa yang diperlukan untuk mewakili array boolean; yaituO(N)
bit.(Sebaliknya, algoritme yang mengandalkan penyortiran atau partisi dalam memori mengasumsikan bahwa Anda dapat mewakili seluruh daftar dalam memori. Dalam bentuk pertanyaan yang diajukan, ini akan membutuhkan
O(N)
kata 64-bit.)Komentar @Jorn bahwa langkah 1 hingga 3 adalah variasi dalam urutan penghitungan. Dalam arti tertentu dia benar, tetapi perbedaannya signifikan:
Xmax - Xmin
penghitungXmax
dengan angka terbesar dalam daftar danXmin
merupakan angka terkecil dalam daftar. Setiap penghitung harus dapat mewakili N status; yaitu mengasumsikan representasi biner itu harus memiliki tipe integer (setidaknya)ceiling(log2(N))
bit.Xmax
danXmin
.ceiling(log2(N)) * (Xmax - Xmin)
bit.Sebaliknya, algoritme yang disajikan di atas hanya membutuhkan
N
bit dalam kasus terburuk dan terbaik.Namun, analisis ini mengarah pada intuisi bahwa jika algoritme membuat awal melewati daftar mencari nol (dan menghitung elemen daftar jika diperlukan), itu akan memberikan jawaban yang lebih cepat tanpa menggunakan spasi sama sekali jika menemukan nol. Ini pasti layak dilakukan jika ada kemungkinan besar untuk menemukan setidaknya satu nol dalam daftar. Dan operan ekstra ini tidak mengubah keseluruhan kompleksitas.
EDIT: Saya telah mengubah deskripsi algoritme untuk menggunakan "array boolean" karena orang-orang tampaknya menganggap deskripsi asli saya menggunakan bit dan bitmap membingungkan.
sumber
bool[]
atau dengan bitmap tidak relevan dengan solusi umum.Karena OP sekarang telah menentukan bahwa daftar asli disimpan dalam RAM dan bahwa komputer hanya memiliki, katakanlah, 1GB memori, saya akan mengambil risiko dan memprediksi bahwa jawabannya adalah nol.
RAM 1GB berarti daftar tersebut dapat memiliki paling banyak 134.217.728 nomor di dalamnya. Tetapi ada 2 64 = 18.446.744.073.709.551.616 kemungkinan nomor. Jadi probabilitas bahwa nol ada dalam daftar adalah 1 dari 137.438.953.472.
Sebaliknya, peluang saya disambar petir tahun ini adalah 1 berbanding 700.000. Dan peluang saya terkena meteorit adalah sekitar 1 banding 10 triliun. Jadi saya sekitar sepuluh kali lebih mungkin untuk ditulis dalam jurnal ilmiah karena kematian saya yang terlalu dini oleh benda langit daripada jawabannya bukan nol.
sumber
Seperti yang ditunjukkan dalam jawaban lain, Anda dapat melakukan penyortiran, lalu memindai hingga Anda menemukan celah.
Anda dapat meningkatkan kompleksitas algoritmik menjadi O (N) dan mempertahankan ruang O (N) dengan menggunakan QuickSort yang dimodifikasi di mana Anda menghilangkan partisi yang bukan kandidat potensial untuk memuat celah.
Ini menghemat banyak perhitungan.
sumber
Untuk mengilustrasikan salah satu perangkap
O(N)
pemikiran, berikut adalahO(N)
algoritma yang menggunakanO(1)
ruang.sumber
Karena angkanya semuanya 64 bit, kita bisa menggunakan radix sort padanya, yaitu O (n). Sortir, lalu pindai hingga Anda menemukan yang Anda cari.
jika angka terkecil adalah nol, pindai ke depan hingga Anda menemukan celah. Jika bilangan terkecil bukan nol, jawabannya nol.
sumber
Untuk metode hemat ruang dan semua nilai berbeda, Anda dapat melakukannya dalam ruang
O( k )
dan waktuO( k*log(N)*N )
. Ini hemat ruang dan tidak ada pemindahan data dan semua operasi adalah dasar (menambahkan pengurangan).U = N; L=0
k
daerah. Seperti ini:0->(1/k)*(U-L) + L
,0->(2/k)*(U-L) + L
,0->(3/k)*(U-L) + L
...0->(U-L) + L
count{i}
) di setiap wilayah. (N*k
langkah)h
) yang tidak penuh. Artinyacount{h} < upper_limit{h}
. (k
langkah)h - count{h-1} = 1
Anda sudah mendapatkan jawabannyaU = count{h}; L = count{h-1}
ini dapat ditingkatkan menggunakan hashing (terima kasih untuk Nic ide ini).
k
daerah. Seperti ini:L + (i/k)->L + (i+1/k)*(U-L)
inc count{j}
menggunakanj = (number - L)/k
(if L < number < U)
h
) yang tidak memiliki elemen k di dalamnyacount{h} = 1
h adalah jawaban AndaU = maximum value in region h
L = minimum value in region h
Ini akan masuk
O(log(N)*N)
.sumber
U-L < k
Saya hanya akan mengurutkannya kemudian menjalankan urutannya sampai saya menemukan celah (termasuk celah di awal antara nol dan angka pertama).
Dalam hal algoritme, sesuatu seperti ini akan melakukannya:
Tentu saja, jika Anda memiliki lebih banyak memori daripada CPU grunt, Anda dapat membuat bitmask dari semua kemungkinan nilai 64-bit dan cukup mengatur bit untuk setiap angka dalam daftar. Kemudian cari 0-bit pertama di bitmask itu. Itu mengubahnya menjadi operasi O (n) dalam hal waktu tetapi sangat mahal dalam hal persyaratan memori :-)
Saya ragu Anda dapat meningkatkan O (n) karena saya tidak dapat melihat cara melakukannya yang tidak melibatkan melihat setiap angka setidaknya sekali.
Algoritme untuk yang satu itu akan berada di sepanjang baris:
sumber
Sortir daftarnya, lihat elemen pertama dan kedua, dan mulai naik hingga ada celah.
sumber
Anda dapat melakukannya dalam O (n) waktu dan O (1) ruang tambahan, meskipun faktor tersembunyinya cukup besar. Ini bukanlah cara praktis untuk menyelesaikan masalah, tetapi mungkin tetap menarik.
Untuk setiap integer 64-bit unsigned (dalam urutan menaik) iterasi daftar sampai Anda menemukan integer target atau Anda mencapai akhir daftar. Jika Anda mencapai akhir daftar, bilangan bulat target adalah bilangan bulat terkecil yang tidak ada dalam daftar. Jika Anda mencapai akhir dari bilangan bulat 64-bit, setiap bilangan bulat 64-bit ada dalam daftar.
Ini dia sebagai fungsi Python:
Fungsi ini sengaja tidak efisien agar tetap O (n). Perhatikan terutama bahwa fungsi tersebut terus memeriksa bilangan bulat target bahkan setelah jawabannya ditemukan. Jika fungsi dikembalikan segera setelah jawabannya ditemukan, berapa kali loop luar berlari akan dibatasi oleh ukuran jawaban, yang dibatasi oleh n. Perubahan itu akan membuat run time menjadi O (n ^ 2), meskipun akan jauh lebih cepat.
sumber
Terima kasih kepada egon, swilden, dan Stephen C untuk inspirasi saya. Pertama, kami mengetahui batasan nilai sasaran karena tidak boleh lebih besar dari ukuran daftar. Selain itu, daftar 1 GB dapat berisi paling banyak 134217728 (128 * 2 ^ 20) bilangan bulat 64-bit.
Bagian
hashing yang saya usulkan menggunakan hashing untuk secara dramatis mengurangi ruang pencarian kami. Pertama, akar kuadrat ukuran list. Untuk daftar 1GB, itu N = 11.586. Siapkan array bilangan bulat berukuran N. Iterasi melalui daftar, dan ambil akar kuadrat * dari setiap angka yang Anda temukan sebagai hash. Di tabel hash Anda, tambahkan penghitung untuk hash itu. Selanjutnya, lakukan iterasi melalui tabel hash Anda. Keranjang pertama yang Anda temukan yang tidak sama dengan ukuran maksimalnya menentukan ruang pencarian baru Anda.
Bagian Bitmap
Sekarang atur peta bit biasa yang sama dengan ukuran ruang pencarian baru Anda, dan ulangi lagi melalui daftar sumber, isi bitmap saat Anda menemukan setiap nomor di ruang pencarian Anda. Setelah selesai, bit pertama yang tidak disetel di bitmap Anda akan memberikan jawaban.
Ini akan diselesaikan dalam ruang O (n) waktu dan O (sqrt (n)).
(* Anda dapat menggunakan sesuatu seperti bit shifting untuk melakukan ini dengan lebih efisien, dan cukup variasikan jumlah dan ukuran bucket yang sesuai.)
sumber
Nah, jika hanya ada satu angka yang hilang dalam daftar angka, cara termudah untuk menemukan angka yang hilang adalah dengan menjumlahkan deretan dan mengurangkan setiap nilai dalam daftar. Nilai akhir adalah angka yang hilang.
sumber
sumber
Kita bisa menggunakan tabel hash untuk menampung angka. Setelah semua angka selesai, jalankan penghitung dari 0 hingga kami menemukan yang terendah. Hash yang cukup baik akan di-hash dan disimpan dalam waktu yang konstan, dan diambil dalam waktu yang konstan.
Kasus terburuk jika ada
n
elemen dalam larik, dan{0, 1, ... n-1}
, dalam hal ini, jawabannya akan diperoleh din
, tetap menyimpannyaO(n)
.sumber
Inilah jawaban saya yang tertulis di Jawa:
Ide Dasar: 1- Loop melalui array membuang duplikat bilangan positif, nol, dan negatif sambil menjumlahkan sisanya, mendapatkan bilangan positif maksimum juga, dan menyimpan bilangan positif unik dalam Peta.
2- Hitung jumlahnya sebagai max * (max + 1) / 2.
3- Temukan perbedaan antara jumlah yang dihitung pada langkah 1 & 2
4- Ulangi lagi dari 1 ke minimum [jumlah selisih, maks] dan kembalikan nomor pertama yang tidak ada di peta yang diisi pada langkah 1.
sumber
Seperti yang ditunjukkan oleh Stephen C dengan cerdik, jawabannya harus berupa angka yang lebih kecil dari panjang array. Saya kemudian akan menemukan jawabannya dengan pencarian biner. Ini mengoptimalkan kasus terburuk (sehingga pewawancara tidak dapat menangkap Anda dalam skenario patologis 'bagaimana jika'). Dalam sebuah wawancara, tunjukkan bahwa Anda melakukan ini untuk mengoptimalkan kasus terburuk.
Cara menggunakan penelusuran biner adalah mengurangi angka yang Anda cari dari setiap elemen larik, dan memeriksa hasil negatif.
sumber
Saya suka pendekatan "tebak nol". Jika angkanya acak, kemungkinan besar nol. Jika "pemeriksa" menyetel daftar non-acak, tambahkan satu dan tebak lagi:
Kasus terburuknya adalah n * N dengan n = N, tetapi dalam praktiknya n sangat mungkin menjadi bilangan kecil (mis. 1)
sumber
Saya tidak yakin apakah saya mendapat pertanyaan itu. Namun jika untuk list 1,2,3,5,6 dan angka yang hilang adalah 4, maka angka yang hilang tersebut dapat ditemukan di O (n) dengan cara: (n + 2) (n + 1) / 2- (n + 1) tidak ada / 2
EDIT: maaf, saya kira saya berpikir terlalu cepat tadi malam. Bagaimanapun, bagian kedua sebenarnya harus diganti dengan sum (daftar), di mana O (n) berasal. Rumusnya mengungkapkan ide di baliknya: untuk n bilangan bulat berurutan, jumlahnya harus (n + 1) * n / 2. Jika ada nomor yang hilang, jumlahnya akan sama dengan jumlah (n + 1) bilangan bulat berurutan dikurangi nomor yang hilang.
Terima kasih telah menunjukkan fakta bahwa saya meletakkan beberapa bagian tengah dalam pikiran saya.
sumber
Bagus Semut Aasma! Saya memikirkan jawabannya selama sekitar 15 menit dan secara independen muncul dengan jawaban yang serupa dengan pemikiran Anda:
m mewakili "kemungkinan keluaran maksimum saat ini mengingat apa yang saya ketahui tentang masukan i pertama dan dengan asumsi tidak ada yang lain tentang nilai-nilai sampai entri di m-1".
Nilai m ini akan dikembalikan hanya jika (a [i], ..., a [m-1]) adalah permutasi dari nilai (i, ..., m-1). Jadi jika a [i]> = m atau jika a [i] <i atau jika a [i] == a [a [i]] kita tahu bahwa m adalah keluaran yang salah dan harus setidaknya satu elemen lebih rendah. Jadi mengurangi m dan menukar a [i] dengan a [m] kita bisa mengulang.
Jika ini tidak benar tetapi a [i]> i maka mengetahui bahwa a [i]! = A [a [i]] kita tahu bahwa menukar [i] dengan [a [i]] akan meningkatkan jumlah elemen di tempat mereka sendiri.
Jika tidak, a [i] harus sama dengan i dalam hal ini kita dapat menaikkan i dengan mengetahui bahwa semua nilai hingga dan termasuk indeks ini sama dengan indeksnya.
Bukti bahwa ini tidak bisa memasuki putaran tak terbatas ditinggalkan sebagai latihan bagi pembaca. :)
sumber
The Dafny fragmen dari Semut jawabannya menunjukkan mengapa algoritma di-tempat mungkin gagal. The
requires
pra-kondisi menjelaskan bahwa nilai-nilai masing-masing item tidak harus melampaui batas-batas array.Tempel kode ke validator dengan dan tanpa
forall ...
klausa untuk melihat kesalahan verifikasi. Kesalahan kedua adalah akibat dari pemverifikasi tidak dapat menetapkan kondisi penghentian untuk loop Lulus 1. Membuktikan ini diserahkan kepada seseorang yang lebih memahami alat tersebut.sumber
Berikut adalah jawaban di Java yang tidak mengubah input dan menggunakan waktu O (N) dan N bit ditambah sedikit overhead memori konstan (di mana N adalah ukuran daftar):
sumber
Dapatkan 100% untuk solusi di atas.
sumber
1) Filter negatif dan Nol
2) Sortir / berbeda
3) Kunjungi array
Kompleksitas : O (N) atau O (N * log (N))
menggunakan Java8
sumber
Sebuah unordered_set dapat digunakan untuk menyimpan semua bilangan positif, dan kemudian kita dapat beralih dari 1 ke panjang unordered_set, dan melihat bilangan pertama yang tidak muncul.
sumber
Solusi melalui javascript dasar
var a = [1, 3, 6, 4, 1, 2]; function findSmallest(a) { var m = 0; for(i=1;i<=a.length;i++) { j=0;m=1; while(j < a.length) { if(i === a[j]) { m++; } j++; } if(m === 1) { return i; } } } console.log(findSmallest(a))
Semoga ini bisa membantu seseorang.
sumber
Dengan python itu bukan yang paling efisien, tapi benar
sumber
sumber
ini dapat membantu:
sumber