Dapatkan 100 angka tertinggi dari daftar tanpa batas

53

Salah satu teman saya ditanyai pertanyaan wawancara ini -

"Ada aliran angka konstan yang datang dari daftar angka tak terbatas yang darinya Anda perlu mempertahankan struktur data untuk mengembalikan 100 angka tertinggi teratas pada suatu titik waktu tertentu. Anggap semua angka itu hanya angka utuh."

Ini sederhana, Anda perlu menyimpan daftar yang diurutkan dalam urutan menurun dan melacak pada nomor terendah dalam daftar itu. Jika nomor baru yang diperoleh lebih besar dari angka terendah itu maka Anda harus menghapus angka terendah itu dan memasukkan nomor baru itu dalam daftar yang disortir sesuai kebutuhan.

Kemudian pertanyaan diperpanjang -

"Bisakah kamu memastikan bahwa Orde untuk penyisipan harus O (1)? Apakah mungkin?"

Sejauh yang saya tahu, bahkan jika Anda menambahkan nomor baru ke daftar dan mengurutkannya lagi menggunakan algoritma apa pun, yang terbaik adalah O (logn) untuk quicksort (saya pikir). Jadi teman saya mengatakan itu tidak mungkin. Tapi dia tidak yakin, dia meminta untuk mempertahankan struktur data lain daripada daftar.

Saya berpikir tentang pohon Biner seimbang, tetapi bahkan di sana Anda tidak akan mendapatkan penyisipan dengan urutan 1. Jadi pertanyaan yang sama saya miliki sekarang juga. Ingin tahu apakah ada struktur data seperti itu yang dapat melakukan penyisipan dalam Urutan 1 untuk masalah di atas atau tidak mungkin sama sekali.

Sachin Shanbhag
sumber
19
Mungkin ini hanya saya salah paham pertanyaan, tetapi mengapa Anda perlu menyimpan daftar diurutkan ? Mengapa tidak melacak nomor terendah, dan jika angka lebih tinggi dari yang ditemui, hapus angka terendah dan masukkan nomor baru, tanpa membuat daftar diurutkan. Itu akan memberi Anda O (1).
EdoDodo
36
@DoDodo - dan setelah operasi itu, bagaimana Anda tahu apa angka terendah yang baru?
Damien_The_Unbeliever
19
Urutkan daftar [O (100 * log (100)) = O (1)] atau lakukan pencarian linier untuk minimum [O (100) = O (1)] untuk mendapatkan angka terendah baru. Daftar Anda adalah ukuran yang konstan, jadi semua operasi ini juga merupakan waktu yang konstan.
Random832
6
Anda tidak harus menjaga seluruh daftar diurutkan. Anda tidak peduli apa angka tertinggi atau tertinggi kedua. Anda hanya perlu tahu apa yang terendah. Jadi setelah Anda memasukkan nomor baru, Anda hanya melintasi 100 angka dan melihat yang sekarang paling rendah. Itu waktu yang konstan.
Tom Zych
27
Urutan asimptotik dari suatu operasi hanya menarik ketika ukuran masalah dapat tumbuh tanpa terikat. Sangat tidak jelas dari pertanyaan Anda kuantitas mana yang tumbuh tanpa batas; sepertinya Anda bertanya apa urutan asimptotik untuk masalah yang ukurannya dibatasi pada 100; itu bahkan bukan pertanyaan yang masuk akal untuk ditanyakan; sesuatu harus tumbuh tanpa terikat. Jika pertanyaannya adalah "bisakah Anda melakukannya untuk mempertahankan n teratas, bukan 100 teratas, dalam waktu O (1)?" maka pertanyaannya masuk akal.
Eric Lippert

Jawaban:

35

Katakanlah k adalah jumlah angka tertinggi yang ingin Anda ketahui (100 dalam contoh Anda). Kemudian, Anda bisa menambahkan nomor baru O(k)yang juga ada O(1). Karena O(k*g) = O(g) if k is not zero and constant.

duedl0r
sumber
6
O (50) adalah O (n), bukan O (1). Memasukkan ke dalam daftar panjang N dalam O (1) waktu berarti waktu tidak tergantung pada nilai N. Itu berarti jika 100 menjadi 10.000, 50 TIDAK boleh menjadi 5000.
18
@hamstergene - tetapi dalam kasus pertanyaan ini, apakah Nukuran daftar yang diurutkan, atau jumlah item yang telah diproses sejauh ini? Jika Anda memproses 10.000 item, dan menyimpan 100 item teratas dalam daftar, atau Anda memproses 1000000000 item, dan menyimpan 100 item teratas dalam daftar yang diurutkan, biaya penyisipan dalam daftar itu tetap sama.
Damien_The_Unbeliever
6
@hamstergene: Kalau begitu Anda salah paham. Dalam link wikipedia Anda ada properti ( "Perkalian dengan konstan"): O(k*g) = O(g) if k not zero and constant. => O(50*1) = O(1).
duedl0r
9
Saya pikir duedl0r benar. Mari kita kurangi masalahnya dan katakan bahwa Anda hanya membutuhkan nilai minimum dan maksimum. Apakah ini O (n) karena minumum dan maksimum adalah 2? (n = 2). Nomor 2 adalah bagian dari definisi masalah. Adalah konstanta, jadi itu ak di O (k * sesuatu) yang setara dengan O (sesuatu)
xanatos
9
@hamstergene: fungsi apa yang kamu bicarakan? nilai 100 tampaknya cukup konstan bagi saya ..
duedl0r
19

Biarkan daftar tidak disortir. Mencari tahu apakah memasukkan nomor baru atau tidak akan memakan waktu lebih lama, tetapi penyisipannya adalah O (1).

Emilio M Bumachar
sumber
7
Saya pikir ini akan memberi Anda penghargaan smart-aleck jika tidak ada yang lain. * 8 ')
Mark Booth
4
@ Emilio, secara teknis Anda benar - dan tentu saja itu adalah jenis yang paling benar ...
Gareth
1
Tetapi Anda juga dapat menyimpan yang terendah dari 100 angka Anda, lalu Anda juga dapat memutuskan apakah Anda harus memasukkan O (1). Maka hanya ketika Anda memasukkan angka, Anda harus mencari angka terendah yang baru. Tetapi itu terjadi lebih jarang daripada memutuskan untuk memasukkan atau tidak, yang terjadi untuk setiap nomor baru.
Andrei Vajna II
12

Ini mudah. Ukuran daftar konstan, oleh karena itu waktu pengurutan daftar adalah konstan. Operasi yang dijalankan dalam waktu konstan dikatakan sebagai O (1). Oleh karena itu pengurutan daftar adalah O (1) untuk daftar ukuran tetap.

Kirk Broadhurst
sumber
9

Setelah Anda melewati 100 angka, biaya maksimum yang akan Anda keluarkan untuk angka berikutnya adalah biaya untuk memeriksa apakah angka itu dalam angka 100 tertinggi (mari kita beri label CheckTime ) ditambah biaya untuk memasukkannya ke dalam set itu dan mengeluarkan terendah (sebut saja EnterTime ), yang merupakan waktu konstan (setidaknya untuk nomor yang dibatasi), atau O (1) .

Worst = CheckTime + EnterTime

Selanjutnya, jika distribusi angka adalah acak, biaya rata-rata berkurang semakin banyak angka yang Anda miliki. Misalnya, peluang Anda harus memasukkan angka ke-101 ke dalam set maksimum adalah 100/101, peluang untuk angka ke-1000 adalah 1/10, dan peluang untuk nomor ke-100 adalah 100 / n. Dengan demikian, persamaan kami untuk biaya rata-rata adalah:

Average = CheckTime + EnterTime / n

Jadi, ketika n mendekati tak terhingga, hanya CheckTime yang penting:

Average = CheckTime

Jika angka-angka terikat, CheckTime adalah konstan, dan karena itu saatnya O (1) .

Jika angka tidak terikat, waktu pemeriksaan akan bertambah dengan lebih banyak angka. Secara teoritis, ini karena jika angka terkecil dalam set maksimum mendapat cukup besar, waktu pemeriksaan Anda akan lebih besar karena Anda harus mempertimbangkan lebih banyak bit. Itu membuatnya tampak akan sedikit lebih tinggi dari waktu yang konstan. Namun, Anda juga bisa berargumen bahwa peluang bahwa angka berikutnya dalam himpunan tertinggi mendekati nol ketika n mendekati tak terhingga sehingga kemungkinan Anda perlu mempertimbangkan lebih banyak bit juga mendekati 0, yang akan menjadi argumen untuk O (1) waktu.

Saya tidak positif, tetapi nyali saya mengatakan bahwa ini saatnya O (log (log)) . Ini karena kemungkinan peningkatan angka terendah adalah logaritmik, dan peluang bahwa jumlah bit yang perlu Anda pertimbangkan untuk setiap cek adalah logaritmik juga. Saya tertarik pada orang lain mengambil ini, karena saya tidak begitu yakin ...

Briguy37
sumber
Kecuali bahwa daftar itu sewenang-wenang, bagaimana jika daftar itu terus bertambah?
dan_waterworth
@dan_waterworth: Jika daftar tak terbatas adalah sangat kebetulan dan kebetulan semakin meningkat (kemungkinannya adalah 1 / ∞!), itu akan cocok dengan skenario terburuk CheckTime + EnterTimeuntuk setiap angka. Ini hanya masuk akal jika angka-angka tidak terikat, dan begitu CheckTimedan EnterTimekeduanya akan meningkat setidaknya secara logaritma karena peningkatan ukuran angka.
Briguy37
1
Jumlahnya tidak acak, ada yang sewenang-wenang. Tidak masuk akal untuk membicarakan peluang.
dan_waterworth
@dan_waterworth: Anda sudah mengatakan dua kali sekarang bahwa angkanya berubah-ubah. Dari mana Anda mendapatkan ini? Selain itu, saya yakin Anda masih dapat menerapkan statistik ke nomor arbitrer yang dimulai dengan case acak, dan meningkatkan keakuratannya karena Anda tahu lebih banyak tentang arbiter. Misalnya, jika Anda adalah wasit, tampaknya akan ada peluang lebih besar untuk memilih angka yang terus meningkat daripada jika, katakanlah, saya adalah wasit;)
Briguy37
7

ini mudah jika Anda tahu Binary Heap Trees . Biner tumpukan mendukung penyisipan dalam waktu konstan rata-rata, O (1). Dan memberi Anda akses mudah ke elemen x pertama.

ratchet freak
sumber
Mengapa menyimpan elemen yang tidak Anda butuhkan? (nilai yang terlalu rendah) Sepertinya algoritma khusus lebih tepat. Tidak mengatakan Anda tidak dapat 'tidak menambahkan' nilai-nilai ketika mereka tidak lebih tinggi dari yang terendah.
Steven Jeuris
Saya tidak tahu, intuisi saya mengatakan kepada saya bahwa tumpukan (dari beberapa rasa) dapat melakukan ini dengan cukup baik. Bukan berarti dia harus menjaga semua elemen untuk melakukannya. Saya tidak merisetnya tetapi "rasanya benar" (TM).
Rig
3
Tumpukan dapat dimodifikasi untuk membuang apa pun di bawah beberapa tingkat mth (untuk tumpukan biner dan k = 100, m akan menjadi 7, karena jumlah simpul = 2 ^ m-1). Ini akan memperlambatnya, tetapi itu akan tetap diamortisasi waktu.
Plutor
3
Jika Anda menggunakan min-heap biner (karena maka bagian atas adalah minimum, yang Anda periksa sepanjang waktu) dan Anda menemukan nomor baru> min, maka Anda harus menghapus elemen atas sebelum Anda dapat memasukkan yang baru . Menghapus elemen atas (min) akan menjadi O (logN) karena Anda harus melintasi setiap tingkat pohon sekali. Jadi secara teknis memang benar bahwa sisipan adalah rata-rata O (1) karena dalam praktiknya masih O (logN) setiap kali Anda menemukan angka> min.
Scott Whitlock
1
@Putor, Anda mengasumsikan beberapa jaminan bahwa tumpukan biner tidak memberi Anda. Memvisualisasikannya sebagai pohon biner, bisa jadi kasus bahwa setiap elemen di cabang kiri lebih kecil daripada elemen di cabang kanan, tetapi Anda mengasumsikan bahwa elemen terkecil terdekat akar.
Peter Taylor
6

Jika dengan pertanyaan pewawancara benar-benar bermaksud untuk bertanya "dapatkah kita memastikan setiap nomor yang masuk diproses dalam waktu yang konstan", maka seperti yang sudah ditunjukkan oleh banyak orang (mis. Lihat jawaban @ duedl0r), solusi teman Anda sudah O (1), dan itu akan terjadi bahkan jika dia menggunakan daftar yang tidak disortir, atau menggunakan semacam gelembung, atau apa pun yang lainnya. Dalam hal ini pertanyaannya tidak masuk akal, kecuali pertanyaan yang sulit atau Anda ingat salah.

Saya menganggap pertanyaan pewawancara itu bermakna, bahwa dia tidak bertanya bagaimana membuat sesuatu menjadi O (1) yang sudah sangat jelas itu.

Karena kompleksitas algoritma pertanyaan hanya masuk akal ketika ukuran input tumbuh tanpa batas, dan satu-satunya input yang dapat tumbuh di sini adalah 100 — ukuran daftar; Saya berasumsi pertanyaan sebenarnya adalah "bisakah kita memastikan kita mendapatkan pengeluaran N Top O (1) waktu per nomor (bukan O (N) seperti dalam solusi teman Anda), apakah mungkin?".

Hal pertama yang terlintas dalam pikiran adalah menghitung sort, yang akan membeli kompleksitas O (1) waktu per angka untuk Top-N-masalah untuk harga menggunakan ruang O (m), di mana m adalah panjang rentang angka yang masuk . Jadi ya, itu mungkin.

hamstergene
sumber
4

Gunakan antrian min-prioritas yang diimplementasikan dengan tumpukan Fibonacci , yang memiliki waktu penyisipan konstan:

1. Insert first 100 elements into PQ
2. loop forever
       n = getNextNumber();
       if n > PQ.findMin() then
           PQ.deleteMin()
           PQ.insert(n)
Gabe Moothart
sumber
4
"Operasi menghapus dan menghapus kerja minimum dalam O(log n)waktu diamortisasi" , jadi ini masih akan menghasilkan di O(log k)mana kjumlah item yang akan disimpan.
Steven Jeuris
1
Ini tidak berbeda dengan jawaban Emilio yang dijuluki "smart-aleck award" karena delete min beroperasi di O (log n) (menurut Wikipedia).
Nicole
@Renesis Jawaban Emilio akan menjadi O (k) untuk menemukan minimum, milikku adalah O (log k)
Gabe Moothart
1
@ Gabe Cukup adil, maksud saya pada prinsipnya. Dengan kata lain, jika Anda tidak menganggap 100 sebagai konstanta, maka jawaban ini juga bukan waktu yang kontan.
Nicole
@Renesis Saya sudah menghapus pernyataan (salah) dari jawabannya.
Gabe Moothart
2

Tugasnya jelas untuk menemukan algoritma yang O (1) dalam panjang N dari daftar angka yang diperlukan. Jadi tidak masalah jika Anda membutuhkan 100 angka teratas atau 10.000 angka, waktu penyisipan harus O (1).

Kuncinya di sini adalah bahwa meskipun persyaratan O (1) disebutkan untuk memasukkan daftar, pertanyaannya tidak mengatakan apa-apa tentang urutan waktu pencarian di seluruh ruang bilangan, tetapi ternyata ini dapat dilakukan O (1) demikian juga. Solusinya adalah sebagai berikut:

  1. Atur hashtable dengan angka untuk kunci dan pasangan pointer daftar tertaut untuk nilai. Setiap pasangan pointer adalah awal dan akhir dari urutan daftar tertaut. Ini biasanya hanya akan menjadi satu elemen kemudian yang berikutnya. Setiap elemen dalam daftar tertaut berada di sebelah elemen dengan angka tertinggi berikutnya. Dengan demikian, daftar tertaut berisi urutan nomor yang disortir. Buat catatan angka terendah.

  2. Ambil nomor baru x dari aliran acak.

  3. Apakah lebih tinggi dari angka terendah yang terakhir dicatat? Ya => Langkah 4, Tidak => Langkah 2

  4. Hit tabel hash dengan nomor yang baru saja diambil. Apakah ada entri? Ya => Langkah 5. Tidak => Ambil nomor baru x-1 dan ulangi langkah ini (ini adalah pencarian linier sederhana, tahan dengan saya di sini, ini dapat ditingkatkan dan saya akan menjelaskan caranya)

  5. Dengan elemen daftar yang baru saja diperoleh dari tabel hash, masukkan nomor baru tepat setelah elemen dalam daftar tertaut (dan perbarui hash)

  6. Ambil angka terendah yang saya rekam (dan hapus dari hash / daftar).

  7. Hit tabel hash dengan nomor yang baru saja diambil. Apakah ada entri? Ya => Langkah 8. Tidak => Ambil nomor baru l + 1 dan ulangi langkah ini (ini adalah pencarian linear sederhana ke atas)

  8. Dengan hit positif, angka tersebut menjadi angka terendah baru. Lanjutkan ke langkah 2

Untuk memungkinkan nilai duplikat, hash sebenarnya perlu mempertahankan awal dan akhir dari urutan daftar elemen yang duplikat. Menambah atau menghapus elemen pada kunci yang diberikan dengan demikian menambah atau mengurangi rentang yang ditunjuk.

Sisipan di sini adalah O (1). Pencarian yang disebutkan adalah, saya kira sesuatu seperti, O (perbedaan rata-rata antara angka). Perbedaan rata-rata meningkat dengan ukuran ruang angka, tetapi berkurang dengan panjang yang diperlukan dari daftar angka.

Jadi strategi pencarian linier sangat buruk, jika jumlah ruang besar (misalnya untuk tipe int 4 byte, 0 hingga 2 ^ 32-1) dan N = 100. Untuk mengatasi masalah kinerja ini, Anda dapat menyimpan kumpulan hashtable paralel, di mana angkanya dibulatkan ke besaran yang lebih tinggi (mis. 1s, 10s, 100s, 1000s) untuk membuat kunci yang sesuai. Dengan cara ini Anda dapat meningkatkan dan menurunkan gigi untuk melakukan pencarian yang dibutuhkan dengan lebih cepat. Kinerja kemudian menjadi O (log numberrange), saya pikir, yang konstan, yaitu O (1) juga.

Untuk memperjelas ini, bayangkan Anda memiliki nomor 197. Anda menekan hash table 10s, dengan '190', itu dibulatkan ke sepuluh terdekat. Apa pun? Tidak. Jadi Anda turun dalam 10-an sampai Anda menekan katakan 120. Kemudian Anda bisa mulai pada 129 dalam hashtable 1s, kemudian coba 128, 127 sampai Anda menekan sesuatu. Anda sekarang telah menemukan di mana dalam daftar tertaut untuk memasukkan nomor 197. Sementara memasukkannya, Anda juga harus memperbarui hashtable 1s dengan entri 197, hass 10s dengan angka 190, 100s dengan 100, dll. Langkah-langkah terbanyak Yang harus Anda lakukan di sini adalah 10 kali log dari kisaran angka.

Saya mungkin salah mengerti, tetapi karena ini adalah pertukaran programmer, dan konteksnya adalah wawancara, saya berharap jawaban di atas adalah jawaban yang cukup meyakinkan untuk situasi itu.

EDIT Saya menambahkan beberapa detail tambahan di sini untuk menjelaskan skema hashtable paralel dan bagaimana artinya pencarian linear yang buruk yang saya sebutkan dapat diganti dengan pencarian O (1). Saya juga menyadari bahwa tentu saja tidak perlu mencari angka terendah berikutnya, karena Anda dapat langsung menuju ke sana dengan melihat hashtable dengan angka terendah dan maju ke elemen berikutnya.

Benediktus
sumber
1
Pencarian harus menjadi bagian dari fungsi sisipan - mereka bukan fungsi independen. Karena pencarian Anda adalah O (n), fungsi sisipan Anda juga O (n).
Kirk Broadhurst
Tidak. Menggunakan strategi yang saya jelaskan, di mana lebih banyak hashtable digunakan untuk melintasi ruang angka lebih cepat, itu adalah O (1). Harap baca jawaban saya lagi.
Benedict
1
@Benedict, jawaban Anda mengatakan cukup jelas bahwa ia memiliki pencarian linier di langkah 4 dan 7. Pencarian linear bukan O (1).
Peter Taylor
Ya, memang, tapi saya urus nanti. Maukah Anda benar-benar membaca sisanya. Jika perlu saya akan mengedit jawaban saya untuk membuatnya sangat jelas.
Benediktus
@Benedict Anda benar - tidak termasuk pencarian, jawaban Anda adalah O (1). Sayangnya solusi ini tidak akan berfungsi tanpa pencarian.
Kirk Broadhurst
1

Bisakah kita berasumsi bahwa angka-angka dari tipe data tetap, seperti Integer? Jika demikian, maka simpan penghitungan dari setiap nomor yang ditambahkan. Ini adalah operasi O (1).

  1. Deklarasikan array dengan elemen sebanyak-banyaknya karena ada angka yang memungkinkan:
  2. Baca setiap nomor saat streaming.
  3. Tally jumlahnya. Abaikan saja jika angka itu sudah dihitung 100 kali karena Anda tidak akan pernah membutuhkannya. Ini mencegah luapan dari penghitungan jumlah yang tak terbatas.
  4. Ulangi dari langkah 2.

VB.Net code:

Const Capacity As Integer = 100

Dim Tally(Integer.MaxValue) As Integer ' Assume all elements = 0
Do
    Value = ReadValue()
    If Tally(Value) < Capacity Then Tally(Value) += 1
Loop

Ketika Anda mengembalikan daftar, Anda dapat mengambil selama yang Anda suka. Cukup beralih dari akhir daftar dan buat daftar baru dari 100 nilai tertinggi yang direkam. Ini adalah operasi O (n), tapi itu tidak relevan.

Dim List(Capacity) As Integer
Dim ListCount As Integer = 0
Dim Value As Integer = Tally.Length - 1
Dim ValueCount As Integer = 0
Do Until ListCount = List.Length OrElse Value < 0
    If Tally(Value) > ValueCount Then
        List(ListCount) = Value
        ValueCount += 1
        ListCount += 1
    Else
        Value -= 1
        ValueCount = 0
    End If
Loop
Return List

Sunting: Sebenarnya, tidak masalah apakah itu tipe data tetap. Karena tidak ada batasan pada konsumsi memori (atau hard disk), Anda dapat membuatnya bekerja untuk berbagai bilangan bulat positif.

Makanan Tangan
sumber
1

Seratus angka mudah disimpan dalam sebuah array, ukuran 100. Pohon apa pun, daftar atau set berlebihan, diberi tugas.

Jika nomor yang masuk lebih tinggi dari yang terendah (= terakhir) dalam array, lewati semua entri. Setelah Anda menemukan yang pertama lebih kecil dari nomor baru Anda (Anda dapat menggunakan pencarian mewah untuk melakukan itu), jalankan melalui sisa array, mendorong setiap entri "turun" oleh satu.

Karena Anda menjaga daftar diurutkan dari awal, Anda tidak perlu menjalankan algoritma pengurutan sama sekali. Ini O (1).

Jörg Z.
sumber
0

Anda bisa menggunakan Binary Max-Heap. Anda harus melacak pointer ke simpul minimum (yang bisa tidak diketahui / null).

Anda mulai dengan memasukkan 100 angka pertama ke dalam tumpukan. Maks akan berada di atas. Setelah ini selesai, Anda akan selalu menyimpan 100 angka di sana.

Lalu ketika Anda mendapatkan nomor baru:

if(minimumNode == null)
{
    minimumNode = findMinimumNode();
}
if(newNumber > minimumNode.Value)
{
    heap.Remove(minimumNode);
    minimumNode = null;
    heap.Insert(newNumber);
}

Sayangnya findMinimumNodeO (n), dan Anda dikenakan biaya satu kali per sisipan (tetapi tidak selama sisipan :). Menghapus simpul minimum dan memasukkan simpul baru, rata-rata, O (1) karena mereka cenderung menuju bagian bawah tumpukan.

Sebaliknya dengan Binary Min-Heap, min berada di atas, yang bagus untuk menemukan min untuk perbandingan, tetapi menyebalkan ketika Anda harus mengganti minimum dengan angka baru yang> min. Itu karena Anda harus menghapus min node (selalu O (logN)) dan kemudian memasukkan simpul baru (rata-rata O (1)). Jadi, Anda masih memiliki O (logN) yang lebih baik daripada Max-Heap, tetapi tidak O (1).

Tentu saja, jika N konstan, maka Anda selalu memiliki O (1). :)

Scott Whitlock
sumber