Cara paling efisien untuk menyimpan ribuan nomor telepon


94

Ini adalah pertanyaan wawancara google:

Ada sekitar seribu nomor telepon yang harus disimpan masing-masing memiliki 10 digit. Anda dapat mengasumsikan 5 digit pertama dari masing-masing angka sama di ribuan angka. Anda harus melakukan operasi berikut: a. Cari jika ada nomor tertentu. b. Cetak semua nomor

Apa cara hemat ruang yang paling efisien untuk melakukan ini?

Saya menjawab tabel hash dan kemudian pengkodean huffman tetapi pewawancara saya mengatakan saya tidak menuju ke arah yang benar. Tolong bantu saya di sini.

Bisakah menggunakan bantuan sufiks trie?

Idealnya, penyimpanan 1000 nomor membutuhkan 4 byte per nomor sehingga dalam semua itu dibutuhkan 4000 byte untuk menyimpan 1000 nomor. Secara kuantitatif, saya ingin mengurangi penyimpanan menjadi <4000 byte, inilah yang dijelaskan pewawancara saya kepada saya.


28
Saya akan menjawab bahwa menggunakan database biasa Anda dapat menyimpannya sebagai teks, bahkan ribuan / jutaan, dan operasi pencarian akan tetap sangat cepat. Saya akan menyarankan untuk tidak melakukan hal-hal yang "pintar" karena seluruh sistem harus dikerjakan ulang jika mereka ingin di masa depan mendukung nomor internasional, atau jika nomor telepon yang dimulai dengan "0" mulai muncul, atau jika pemerintah memutuskan untuk mengubah format nomor telepon, dan sebagainya.
Thomas Bonini

1
@AndreasBonini: Saya mungkin akan memberikan jawaban itu, kecuali jika saya mewawancarai di perusahaan seperti Google atau Facebook, solusi di luar kotak, jangan memotongnya. Meskipun postgres misalnya telah mencoba, juga, saya tidak akan yakin bahwa ini memotong throughput data yang perlu diambil oleh google.
LiKao

1
@LiKao: perlu diingat bahwa OP secara khusus menyatakan "sekitar seribu angka"
Thomas Bonini

@AndreasBonini: Benar, mungkin juga merupakan tes, bahwa orang yang diwawancarai tahu untuk menafsirkan batasan tersebut dengan benar dan memilih solusi terbaik sesuai dengan ini.
LiKao

4
"efisien" dalam pertanyaan ini benar-benar perlu didefinisikan - efisien dengan cara apa? ruang, waktu, keduanya?
matt b

Jawaban:


36

Berikut perbaikan dari jawaban aix . Pertimbangkan untuk menggunakan tiga "lapisan" untuk struktur data: yang pertama adalah konstanta untuk lima digit pertama (17 bit); jadi mulai saat ini, setiap nomor telepon hanya memiliki sisa lima digit. Kami melihat lima digit yang tersisa ini sebagai bilangan bulat biner 17-bit dan menyimpan k dari bit tersebut menggunakan satu metode dan 17 - k = m dengan metode yang berbeda, menentukan k pada akhirnya untuk meminimalkan ruang yang diperlukan.

Pertama-tama kami mengurutkan nomor telepon (semuanya dikurangi menjadi 5 digit desimal). Kemudian kita hitung ada berapa nomor telepon yang mana bilangan biner yang terdiri dari m bit pertama semuanya 0, untuk berapa nomor telepon m bit pertama paling banyak 0 ... 01, untuk berapa nomor telepon m pertama bit paling banyak 0 ... 10, dan sebagainya, hingga hitungan nomor telepon yang m bit pertamanya adalah 1 ... 11 - hitungan terakhir ini adalah 1000 (desimal). Ada 2 ^ m hitungan seperti itu dan setiap hitungan maksimal 1000. Jika kita menghilangkan yang terakhir (karena kita tahu itu 1000), kita dapat menyimpan semua angka ini dalam blok yang berdekatan (2 ^ m - 1) * 10 bit. (10 bit cukup untuk menyimpan angka kurang dari 1024.)

K bit terakhir dari semua nomor telepon (dikurangi) disimpan berdekatan dalam memori; jadi jika k adalah, katakanlah, 7, maka 7 bit pertama dari blok memori ini (bit 0 hingga 6) sesuai dengan 7 bit terakhir dari nomor telepon pertama (dikurangi), bit 7 hingga 13 sesuai dengan 7 bit terakhir nomor telepon kedua (dikurangi), dan sebagainya. Ini membutuhkan 1000 * k bit dengan total 17 + (2 ^ (17 - k ) - 1) * 10 + 1000 * k , yang mencapai minimum 11287 untuk k = 10. Jadi kita bisa menyimpan semua nomor telepon di langit-langit ( 11287/8) = 1411 byte.

Ruang tambahan dapat dihemat dengan mengamati bahwa tidak ada bilangan kita yang dapat dimulai dengan misalnya 1111111 (biner), karena bilangan terendah yang dimulai dengan itu adalah 130048 dan kita hanya memiliki lima digit desimal. Hal ini memungkinkan kita untuk memotong beberapa entri dari blok memori pertama: alih-alih 2 ^ m - 1 hitungan, kita hanya membutuhkan ceil (99999/2 ^ k ). Artinya rumusnya menjadi

17 + ceil (99999/2 ^ k ) * 10 + 1000 * k

yang luar biasa mencapai minimum 10997 untuk keduanya k = 9 dan k = 10, atau ceil (10997/8) = 1375 byte.

Jika kita ingin mengetahui apakah nomor telepon tertentu ada di himpunan kita, pertama kita periksa apakah lima digit biner pertama cocok dengan lima digit yang telah kita simpan. Kemudian kita membagi lima digit yang tersisa menjadi m = 7 bit teratas (yaitu, katakanlah, angka m- bit M ) dan k = 10 bit yang lebih rendah (angka K ). Sekarang kita menemukan nomor a [M-1] dari nomor telepon yang dikurangi yang m digit pertamanya paling banyak M - 1, dan nomor a [M] dari nomor telepon yang dikurangi yang m digit pertamanya paling banyak M , keduanya dari blok bit pertama. Kami sekarang memeriksa antara a [M-1] dan a [M] th urutan k bit pada blok kedua memori untuk melihat apakah kita menemukan K ; dalam kasus terburuk ada 1000 urutan seperti itu, jadi jika kita menggunakan pencarian biner kita dapat menyelesaikan operasi O (log 1000).

Pseudocode untuk mencetak semua 1000 nomor berikut, di mana saya mengakses K 'th k -bit masuknya blok pertama dari memori sebagai sebuah [K] dan M ' th m entri-bit dari blok kedua dari memori b [M] (keduanya akan membutuhkan sedikit operasi yang membosankan untuk ditulis). Lima digit pertama ada di bilangan c .

i := 0;
for K from 0 to ceil(99999 / 2^k) do
  while i < a[K] do
    print(c * 10^5 + K * 2^k + b[i]);
    i := i + 1;
  end do;
end do;

Mungkin ada yang salah dengan kasus batas untuk K = ceil (99999/2 ^ k ), tapi itu cukup mudah untuk diperbaiki.

Akhirnya, dari sudut pandang entropi, tidak mungkin untuk menyimpan bagian dari 10 ^ 3 bilangan bulat positif yang semuanya kurang dari 10 ^ 5 dalam kurang dari ceil (log [2] (binomial (10 ^ 5, 10 ^ 3)) ) = 8073. Termasuk 17 yang kita butuhkan untuk 5 digit pertama, masih ada selisih 10997 - 8090 = 2907 bit. Merupakan tantangan yang menarik untuk melihat apakah ada solusi yang lebih baik di mana Anda masih dapat mengakses nomor dengan relatif efisien!


4
Struktur data yang Anda gambarkan di sini sebenarnya hanyalah versi trie yang sangat efisien, yang hanya menggunakan sesedikit yang diperlukan untuk pengindeksan dan hanya dua level. Dalam praktiknya, akan menyenangkan untuk melihat apakah ini dapat mengalahkan trie dengan lebih banyak level, tetapi saya pikir ini sangat tergantung pada distribusi nomor (Dalam nomor telepon langsung sebenarnya tidak sepenuhnya acak, tetapi hanya hampir).
LiKao

Hai Erik, karena Anda mengatakan ingin melihat alternatif lain, lihat solusi saya. Ini menyelesaikannya dalam 8.580 bit, yang hanya 490 bit dari minimum teoretis. Agak tidak efisien untuk mencari nomor satu per satu, tetapi penyimpanannya sangat kompak.
Briguy37

1
Saya beranggapan bahwa pewawancara yang waras akan lebih memilih jawaban "a trie" daripada "database yang dibuat khusus dan rumit". Jika Anda ingin menunjukkan keahlian hacking 133t Anda, Anda dapat menambahkan - "itu mungkin untuk membuat algoritma pohon tertentu untuk kasus khusus ini, jika diperlukan."
KarlP

Hai, Bisakah Anda menjelaskan bagaimana 5 digit mengambil 17 bit untuk disimpan?
Tushar Banne

@tushar Lima digit menyandikan angka antara 00000 dan 99999 inklusif. Mewakili angka itu dalam biner. 2 ^ 17 = 131072, jadi 17 bit cukup untuk itu tetapi 16 tidak.
Erik P.

43

Berikut ini, saya memperlakukan angka sebagai variabel integer (sebagai lawan string):

  1. Urutkan angkanya.
  2. Pisahkan setiap angka menjadi lima digit pertama dan lima digit terakhir.
  3. Lima digit pertama sama di semua angka, jadi simpan saja satu kali. Ini akan membutuhkan penyimpanan 17 bit.
  4. Simpan lima digit terakhir dari setiap nomor satu per satu. Ini akan membutuhkan 17 bit per nomor.

Ringkasnya: 17 bit pertama adalah awalan umum, 1000 grup berikutnya yang terdiri dari 17 bit adalah lima digit terakhir dari setiap nomor yang disimpan dalam urutan menaik.

Secara total kami melihat 2128 byte untuk 1000 nomor, atau 17.017 bit per 10 digit nomor telepon.

Pencarian adalah O(log n)(pencarian biner) dan enumerasi lengkap adalah O(n).


Uhm, kompleksitas ruangnya mana?
aioobe

Terlalu banyak waktu untuk membangun (O (log (n) * n k) (k adalah panjang) untuk penyortiran, dibandingkan dengan O (n k) untuk membangun sebuah trie). Ruang juga jauh dari optimal, karena prefiks umum yang lebih panjang disimpan secara individual. Waktu pencarian juga belum optimal. Untuk data string seperti ini, mudah untuk melupakan panjang angka yang mendominasi pencarian. Yaitu pencarian biner adalah O (log (n) * k), sedangkan trie hanya membutuhkan O (k). Anda dapat mengurangi ekspresi ini, ketika k konstan, tetapi ini untuk menunjukkan masalah umum ketika mempertimbangkan tentang struktur data yang menyimpan string.
LiKao

@LiKao: Siapa yang mengatakan sesuatu tentang string? Saya berurusan secara eksklusif dengan variabel integer jadi ktidak relevan.
NPE

1
Oke, saya salah membaca jawabannya. Namun, bagian umum tidak disimpan bersama, jadi poin tentang efisiensi ruang tetap ada. Untuk 1000 dari 5 digit angka, akan ada cukup banyak awalan umum, jadi menguranginya akan sangat membantu. Juga dalam kasus bilangan kita memiliki O (log (n)) versus O (k) untuk string, yang masih lebih cepat.
LiKao

1
@Geek: 1001 grup 17 bit adalah 17017 bit atau 2128 byte (dengan beberapa perubahan).
NPE

22

http://en.wikipedia.org/wiki/Acyclic_deterministic_finite_automaton

Saya pernah melakukan wawancara dimana mereka menanyakan tentang struktur data. Saya lupa "Array".


1
+1 pasti cara yang harus dilakukan. Saya mempelajarinya di bawah nama lain, pohon perpustakaan atau pohon pencarian leksikal atau sesuatu ketika saya masih mahasiswa (jika seseorang mengingat nama lama itu tolong beritahu).
Valmond

6
Ini tidak memenuhi persyaratan 4000 byte. Untuk penyimpanan penunjuk saja, skenario terburuknya adalah Anda memerlukan 1 penunjuk untuk 1-4 daun ke tingkat berikutnya, 10 penunjuk untuk ke-5, 100 untuk ke-6, dan 1000 untuk tingkat ke-7, ke-8, dan ke-9 , yang membuat total pointer kita menjadi 3114. Itu memberikan setidaknya 3114 lokasi memori berbeda yang dibutuhkan oleh pointer, yang berarti Anda memerlukan setidaknya 12 bit untuk setiap pointer. 12 * 3114 = 37368 bit = 4671 byte> 4000 byte, dan itu bahkan tidak menggambarkan bagaimana Anda merepresentasikan nilai setiap daun!
Briguy37

16

Saya mungkin akan mempertimbangkan untuk menggunakan beberapa versi terkompresi dari Trie (mungkin DAWG seperti yang disarankan oleh @Misha).

Itu secara otomatis akan memanfaatkan fakta bahwa mereka semua memiliki awalan yang sama.

Pencarian akan dilakukan dalam waktu konstan, dan pencetakan akan dilakukan dalam waktu linier.


Pertanyaannya adalah tentang cara paling hemat ruang untuk menyimpan data. Maukah Anda memberikan perkiraan berapa banyak ruang yang dibutuhkan metode ini untuk 1000 nomor telepon? Terima kasih.
NPE

Spasi untuk trie paling banyak O (n * k) dimana n adalah banyaknya string dan k adalah panjang tiap string. Mempertimbangkan bahwa Anda tidak memerlukan karakter 8 bit untuk mewakili angka, saya sarankan untuk menyimpan 4 indeks heksadesimal heksadesimal dan satu untuk bit yang tersisa. Dengan cara ini Anda membutuhkan maksimal 17 bit per nomor. Karena Anda dalam semua kasus akan memiliki bentrokan di semua level dengan pengkodean ini, Anda benar-benar bisa mendapatkan di bawah ini. Berharap kita menyimpan 1000 angka, kita sudah bisa menyimpan total 250 bit untuk bentrokan di tingkat pertama. Uji terbaik pengkodean yang benar pada data contoh.
LiKao

@LiKao, benar, dan dengan mencatat bahwa, misalnya, 1000 angka tidak boleh memiliki lebih dari 100 dua digit terakhir yang berbeda, trie dapat diciutkan secara signifikan pada level terakhir.
aioobe

@aioobe: Daunnya bisa roboh di tingkat terakhir karena tidak ada anak. Namun, daun pada level kedua hingga terakhir membutuhkan 2 ^ 10 = 1024 status (setiap digit terakhir bisa hidup atau mati), jadi tidak dapat direduksi dalam kasus ini karena hanya ada 1000 angka. Ini berarti jumlah petunjuk kasus terburuk tetap di 3114 (lihat komentar saya tentang jawaban Misha) sementara daun yang dibutuhkan pergi ke 5 + 10 + 100 + 1000 + 1000 + 10 = 2125, yang tidak mengubah 12 byte yang dibutuhkan untuk masing-masing penunjuk. Jadi, ini masih menempatkan solusi trie pada 4671 byte dengan mempertimbangkan hanya pointer saja.
Briguy37

@ Briguy37, tidak yakin saya mengerti argumen " setiap digit terakhir bisa hidup atau mati ". Semua angka terdiri dari 10 digit, bukan?
aioobe

15

Saya pernah mendengar masalah ini sebelumnya (tetapi tanpa asumsi 5 digit pertama sama), dan cara termudah untuk melakukannya adalah Pengkodean Beras :

1) Karena urutannya tidak masalah, kita dapat mengurutkannya, dan hanya menyimpan perbedaan antara nilai yang berurutan. Dalam kasus kami, perbedaan rata-rata adalah 100.000 / 1000 = 100

2) Menyandikan perbedaan menggunakan kode Beras (basis 128 atau 64) atau bahkan kode Golomb (basis 100).

EDIT: Perkiraan untuk pengkodean Beras dengan basis 128 (bukan karena akan memberikan hasil terbaik, tetapi karena lebih mudah untuk dihitung):

Kami akan menyimpan nilai pertama apa adanya (32 bit).
999 nilai lainnya adalah perbedaan (kami berharap nilainya kecil, rata-rata 100) akan berisi:

nilai unary value / 128(jumlah variabel bit + 1 bit sebagai terminator)
nilai biner untuk value % 128(7 bit)

Kita harus memperkirakan entah bagaimana batasnya (sebut saja VBL) untuk jumlah bit variabel:
batas bawah: anggap kita beruntung, dan tidak ada perbedaan yang lebih besar dari basis kita (128 dalam kasus ini). ini berarti memberikan 0 bit tambahan.
batas tinggi: karena semua perbedaan yang lebih kecil dari basis akan dikodekan dalam bagian bilangan biner, jumlah maksimum yang perlu kita enkode dalam unary adalah 100000/128 = 781.25 (bahkan lebih kecil lagi, karena kita tidak mengharapkan sebagian besar perbedaan menjadi nol ).

Jadi, hasilnya adalah 32 + 999 * (1 + 7) + variabel (0..782) bits = 1003 + variabel (0..98) byte.


Dapatkah Anda memberikan detail lebih lanjut tentang cara Anda melakukan enkode dan tentang penghitungan ukuran akhir. 1101 byte atau 8808 bit tampaknya sangat dekat dengan batas teoretis 8091 bit, jadi saya sangat terkejut, bahwa dalam praktiknya adalah mungkin untuk mencapai sesuatu seperti ini.
LiKao

Bukankah itu 32 + 999 * (1 + 7 + variable(0..782))potongan - potongan? Masing-masing dari 999 angka membutuhkan representasi value / 128.
Kirk Broadhurst

1
@ Kirk: tidak, jika semuanya di kisaran 5 digit. Ini karena kami berharap bahwa jumlah semua perbedaan ini (ingat, kami menyandikan perbedaan antara nilai-nilai yang berurutan, bukan antara nilai pertama dan ke-N) akan di bawah 100000 (bahkan dalam skenario kasus terburuk)
ruslik

Anda membutuhkan 34 bit, bukan 32 bit untuk mewakili nilai pertama (9.999.999.999> 2 ^ 32 = 4.294.967.296). Selain itu, perbedaan maksimum adalah 00000 hingga 99001 karena nomornya unik, yang akan menambahkan 774 1 bukan 782 untuk basis 128. Jadi kisaran penyimpanan 1.000 angka untuk basis 128 adalah 8026-8800 bit atau 1004-1100 byte. Basis 64-bit memberikan penyimpanan yang lebih baik, dengan rentang dari 879-1072 byte.
Briguy37

1
@raisercostin: ini yang ditanyakan Kirk. Dalam contoh Anda, dengan mengenkode sekali perbedaan 20k antara dua nilai pertama, hanya 80k rentang maksimum yang mungkin terjadi di masa mendatang. Ini akan menggunakan 20k / 128 = 156 bit unary dari maksimum 782 (yang sesuai dengan 100k)
ruslik

7

Ini adalah masalah yang diketahui dengan baik dari Bentley's Programming Pearls.

Solusi: Hapus lima digit pertama dari angka-angka tersebut karena sama untuk setiap angka. Kemudian gunakan operasi bitwise untuk mewakili 9999 kemungkinan nilai yang tersisa. Anda hanya membutuhkan 2 ^ 17 Bit untuk mewakili angka-angka tersebut. Setiap Bit mewakili angka. Jika bit disetel, nomornya ada di buku telepon.

Untuk mencetak semua angka, cukup cetak semua angka yang bitnya diset dengan awalan. Untuk mencari angka tertentu, lakukan aritmatika bit yang diperlukan untuk memeriksa representasi bitwise dari angka tersebut.

Anda dapat mencari angka dalam O (1) dan efisiensi ruang maksimal karena representasi bit.

HTH Chris.


3
Ini akan menjadi pendekatan yang baik untuk kumpulan angka yang padat. Sayangnya, di sini himpunannya sangat jarang: hanya ada 1.000 nomor dari kemungkinan 100.000. Oleh karena itu, pendekatan ini rata-rata membutuhkan 100 bit per nomor. Lihat jawaban saya untuk alternatif yang hanya membutuhkan ~ 17 bit.
NPE

1
Bukankah waktu yang dibutuhkan untuk mencetak semua angka sebanding dengan 100.000, bukan 1.000?
aioobe

Menggabungkan dua ide pada dasarnya Anda akan langsung mendapatkan trie. Menggunakan bitvector dengan 100.000 entri adalah cara mencari lokasi secara keseluruhan dan menghabiskan banyak ruang. Bagaimanapun juga, pencarian O (log (n)) seringkali terlalu lambat (tergantung pada jumlah query di sini). Jadi dengan menggunakan hierachie kumpulan bit untuk pengindeksan, Anda akan menyimpan maksimum 17 bit per nomor, sambil tetap mendapatkan pencarian O (1). Beginilah cara kerja trie. Juga waktu untuk mencetak dalam O (n) untuk trie, yang diwarisi dari kasus yang diurutkan.
LiKao

Ini bukan "cara hemat ruang yang paling efisien untuk melakukan ini".
Jake Berger

5

Penyimpanan tetap 1073 byte untuk 1.000 nomor:

Format dasar metode penyimpanan ini adalah menyimpan 5 digit pertama, hitungan untuk setiap grup, dan offset untuk setiap angka di setiap grup.

Awalan: Awalan
5 digit kami menempati 17 bit pertama .

Pengelompokan:
Selanjutnya, kita perlu mencari pengelompokan ukuran yang baik untuk angka. Mari kita coba memiliki sekitar 1 nomor per kelompok. Karena kita tahu ada sekitar 1000 nomor untuk disimpan, kita membagi 99.999 menjadi sekitar 1000 bagian. Jika kita memilih ukuran grup sebagai 100, akan ada bit yang terbuang, jadi mari kita coba ukuran grup 128, yang dapat direpresentasikan dengan 7 bit. Ini memberi kita 782 grup untuk dikerjakan.

Hitungan:
Selanjutnya, untuk masing-masing dari 782 grup, kita perlu menyimpan jumlah entri di setiap grup. Hitungan 7-bit untuk setiap grup akan menghasilkan 7*782=5,474 bits, yang sangat tidak efisien karena jumlah rata-rata yang diwakili adalah sekitar 1 karena cara kami memilih grup kami.

Jadi, sebagai gantinya kita memiliki jumlah ukuran variabel dengan 1 untuk setiap nomor dalam kelompok diikuti oleh 0. Jadi, jika kita memiliki xangka dalam kelompok, kita akan x 1'sdiikuti oleh a 0untuk mewakili hitungan. Misalnya, jika kita memiliki 5 angka dalam satu grup, hitungannya akan diwakili 111110. Dengan metode ini, jika ada 1.000 bilangan kita berakhir dengan 1000 1 dan 782 0 dengan total 1000 + 782 = 1.782 bit untuk hitungan .

Offset:
Terakhir, format setiap angka hanya akan menjadi offset 7-bit untuk setiap grup. Misalnya, jika 00000 dan 00001 adalah satu-satunya bilangan dalam grup 0-127, bit untuk grup itu adalah 110 0000000 0000001. Dengan asumsi 1.000 angka, akan ada 7.000 bit untuk offset .

Jadi penghitungan akhir kita dengan asumsi 1.000 angka adalah sebagai berikut:

17 (prefix) + 1,782 (counts) + 7,000 (offsets) = 8,799 bits = 1100 bytes

Sekarang, mari kita periksa apakah pemilihan ukuran grup kami dengan membulatkan ke 128 bit adalah pilihan terbaik untuk ukuran grup. Memilih xjumlah bit untuk mewakili setiap grup, rumus ukurannya adalah:

Size in bits = 17 (prefix) + 1,000 + 99,999/2^x + x * 1,000

Meminimalkan persamaan ini untuk nilai integer dari xmemberi x=6, yang menghasilkan 8580 bit = 1073 byte . Jadi, penyimpanan ideal kami adalah sebagai berikut:

  • Ukuran grup: 2 ^ 6 = 64
  • Jumlah kelompok: 1.562
  • Penyimpanan total:

    1017 (prefix plus 1's) + 1563 (0's in count) + 6*1000 (offsets) = 8,580 bits = 1,073 bytes


1

Mengambil ini sebagai masalah teoritis murni dan meninggalkan asas implementasi, satu-satunya cara paling efisien adalah dengan hanya mengindeks semua set yang mungkin dari 10.000 digit terakhir dalam tabel pengindeksan raksasa. Dengan asumsi Anda memiliki tepat 1000 angka, Anda akan membutuhkan lebih dari 8000 bit untuk mengidentifikasi set saat ini secara unik. Tidak ada kemungkinan kompresi yang lebih besar, karena Anda akan memiliki dua set yang diidentifikasi dengan status yang sama.

Masalah dengan ini adalah, bahwa Anda harus mewakili masing-masing dari 2 ^ 8000 set dalam program Anda sebagai lut, dan bahkan google tidak akan mampu melakukan ini dari jarak jauh.

Pencarian akan menjadi O (1), mencetak semua nomor O (n). Penyisipan akan menjadi O (2 ^ 8000) yang secara teori adalah O (1), tetapi dalam praktiknya tidak dapat digunakan.

Dalam sebuah wawancara saya hanya akan memberikan jawaban ini, jika saya yakin, bahwa perusahaan sedang mencari seseorang yang mampu banyak berpikir di luar kotak. Jika tidak, ini mungkin membuat Anda terlihat seperti ahli teori tanpa masalah dunia nyata.

EDIT : Ok, ini salah satu "implementasi".

Langkah-langkah untuk membangun implementasi:

  1. Ambil array konstan dengan ukuran 100 000 * (1000 pilih 100 000) bit. Ya, saya menyadari fakta bahwa susunan ini akan membutuhkan lebih banyak ruang daripada atom di alam semesta dengan beberapa besaran.
  2. Pisahkan larik besar ini menjadi potongan masing-masing 100.000.
  3. Di setiap penyimpanan potongan bit array untuk satu kombinasi spesifik dari lima digit terakhir.

Ini bukan program, tetapi semacam program meta, yang akan membangun LUT raksasa yang sekarang dapat digunakan dalam program. Hal konstan dari program biasanya tidak dihitung saat menghitung efisiensi ruang, jadi kami tidak peduli dengan larik ini, saat melakukan penghitungan akhir.

Berikut cara menggunakan LUT ini:

  1. Ketika seseorang memberi Anda 1000 angka, Anda menyimpan lima digit pertama secara terpisah.
  2. Cari tahu bagian mana dari larik Anda yang cocok dengan set ini.
  3. Simpan nomor himpunan dalam satu nomor 8074 bit (sebut ini c).

Artinya untuk penyimpanan kita hanya membutuhkan 8091 bits, yang telah kita buktikan disini sebagai encoding yang optimal. Namun menemukan bongkahan yang benar membutuhkan O (100000 * (100 000 pilih 1000)), yang menurut aturan matematika adalah O (1), tetapi dalam praktiknya akan selalu memakan waktu lebih lama daripada waktu alam semesta.

Pencariannya sederhana:

  1. strip dari lima digit pertama (nomor yang tersisa akan disebut n ').
  2. uji apakah mereka cocok
  3. Hitung i = c * 100000 + n '
  4. Periksa apakah bit di i di LUT disetel ke satu

Mencetak semua angka juga sederhana (dan membutuhkan O (100000) = O (1) sebenarnya, karena Anda selalu harus memeriksa semua bit dari potongan saat ini, jadi saya salah menghitung ini di atas).

Saya tidak akan menyebut ini sebagai "implementasi", karena mengabaikan batasan secara terang-terangan (ukuran alam semesta dan waktu alam semesta ini hidup atau bumi ini akan ada). Namun secara teori ini adalah solusi optimal. Untuk masalah yang lebih kecil, ini sebenarnya bisa dilakukan, dan terkadang akan dilakukan. Misalnya jaringan pengurutan adalah contoh untuk cara pengkodean ini, dan dapat digunakan sebagai langkah terakhir dalam algoritma pengurutan rekursif, untuk mendapatkan percepatan yang besar.


1
Apa cara hemat ruang yang paling efisien untuk melakukan ini?
Sven

1
Saat melakukan kalkulasi ruang waktu proses, ini dapat dengan mudah dibuktikan sebagai cara penghematan ruang yang paling efisien, karena Anda menghitung kemungkinan status sistem hanya dengan satu angka. Tidak ada pengkodean yang lebih kecil untuk masalah ini. Trik untuk jawaban ini adalah, bahwa ukuran program hampir tidak pernah dipertimbangkan, saat melakukan perhitungan (cobalah untuk menemukan jawaban yang memperhitungkan hal ini, dan Anda akan mengerti maksud saya). Jadi untuk masalah apa pun yang memiliki batasan ukuran, Anda selalu dapat menghitung semua status, untuk mendapatkan cara yang paling menghemat ruang untuk menanganinya.
LiKao

1

Ini sama dengan menyimpan seribu bilangan bulat non-negatif masing-masing kurang dari 100.000. Kita dapat menggunakan sesuatu seperti pengkodean aritmatika untuk melakukan ini.

Akhirnya, nomor-nomor itu akan disimpan dalam daftar yang diurutkan. Saya perhatikan bahwa perbedaan yang diharapkan antara nomor yang berdekatan dalam daftar adalah 100.000 / 1000 = 100, yang dapat direpresentasikan dalam 7 bit. Ada juga banyak kasus di mana diperlukan lebih dari 7 bit. Cara sederhana untuk merepresentasikan kasus yang kurang umum ini adalah dengan mengadopsi skema utf-8 di mana satu byte mewakili integer 7-bit kecuali bit pertama disetel, dalam hal ini byte berikutnya dibaca untuk menghasilkan integer 14-bit, kecuali -nya bit pertama diatur, dalam hal byte berikutnya adalah membaca untuk mewakili bilangan bulat 21-bit.

Jadi setidaknya setengah dari perbedaan antara bilangan bulat berurutan dapat direpresentasikan dengan satu byte, dan hampir semua sisanya memerlukan dua byte. Beberapa angka, dipisahkan oleh perbedaan yang lebih besar dari 16.384, akan membutuhkan tiga byte, tetapi tidak boleh lebih dari 61. Penyimpanan rata-rata kemudian akan menjadi sekitar 12 bit per nomor, atau sedikit kurang, atau paling banyak 1500 byte.

Kelemahan dari pendekatan ini adalah memeriksa keberadaan sebuah bilangan sekarang adalah O (n). Namun, tidak ada persyaratan kompleksitas waktu yang ditentukan.

Setelah menulis, saya perhatikan ruslik sudah menyarankan perbedaan metode di atas, satu-satunya perbedaan adalah skema pengkodean. Milik saya kemungkinan lebih sederhana tetapi kurang efisien.


1

Hanya untuk menanyakan dengan cepat alasan apa pun yang kami tidak ingin mengubah angka menjadi basis 36. Ini mungkin tidak menghemat banyak ruang tetapi pasti akan menghemat waktu dalam pencarian karena u akan melihat jauh kurang dari 10 digit. Atau saya akan membaginya menjadi beberapa file tergantung pada masing-masing kelompok. jadi saya akan memberi nama file (111) -222.txt dan kemudian saya hanya akan menyimpan nomor yang sesuai dengan grup itu di sana dan kemudian membuatnya dapat dicari dalam urutan numerik dengan cara ini saya selalu dapat memeriksa untuk melihat apakah file tersebut keluar. sebelum saya melakukan pencarian yang lebih besar. atau agar benar saya akan menjalankan pencarian biner satu untuk file untuk melihat apakah itu keluar. dan pencarian bonary lain pada konten file


0

Mengapa tidak tetap sederhana? Gunakan array struct.

Jadi kita bisa menyimpan 5 digit pertama sebagai konstanta, jadi lupakan itu untuk saat ini.

65535 adalah yang paling banyak yang dapat disimpan dalam nomor 16-bit, dan jumlah maksimum yang dapat kita miliki adalah 99999, yang sesuai dengan nomor bit ke-17 dengan jumlah maksimum 131071.

Menggunakan tipe data 32-bit adalah pemborosan karena kita hanya membutuhkan 1 bit dari 16-bit ekstra itu ... oleh karena itu, kita dapat mendefinisikan struktur yang memiliki boolean (atau karakter) dan bilangan 16-bit ..

Dengan asumsi C / C ++

typedef struct _number {

    uint16_t number;
    bool overflow;
}Number;

Struct ini hanya membutuhkan 3-byte, dan kita membutuhkan array 1000, jadi total 3000 byte. Kami telah mengurangi total ruang sebesar 25%!

Sejauh menyimpan angka, kita bisa melakukan matematika bitwise sederhana

overflow = (number5digits & 0x10000) >> 4;
number = number5digits & 0x1111;

Dan kebalikannya

//Something like this should work
number5digits = number | (overflow << 4);

Untuk mencetak semuanya, kita dapat menggunakan loop sederhana di atas array. Mengambil nomor tertentu tentu saja terjadi dalam waktu konstan, karena ini adalah larik.

for(int i=0;i<1000;i++) cout << const5digits << number5digits << endl;

Untuk mencari nomor, kita menginginkan array yang diurutkan. Jadi, ketika nomor disimpan, urutkan array (saya akan memilih jenis gabungan secara pribadi, O (nlogn)). Sekarang untuk mencari, saya akan menggunakan pendekatan semacam gabungan. Pisahkan array, dan lihat di antara nomor mana kita berada. Kemudian panggil fungsi hanya pada larik itu. Lakukan ini secara berulang hingga Anda memiliki kecocokan dan mengembalikan indeks, jika tidak, indeks tidak ada dan mencetak kode kesalahan. Pencarian ini akan cukup cepat, dan kasus terburuk masih lebih baik daripada O (nlogn) karena ini benar-benar akan dieksekusi dalam waktu yang lebih singkat daripada jenis gabungan (hanya mengulangi 1 sisi pemisahan setiap kali, bukan kedua sisi :)), yang mana adalah O (nlogn).


0

Solusi saya: kasus terbaik 7,025 bit / angka, kasus terburuk 14,193 bit / angka, rata-rata kasar 8,551 bit / angka. Stream-encoded, tidak ada akses acak.

Bahkan sebelum membaca jawaban ruslik, saya langsung terpikir untuk menyandikan perbedaan antara tiap angka, karena jumlahnya akan kecil dan harus relatif konsisten, tetapi solusinya juga harus dapat mengakomodasi skenario terburuk. Kami memiliki ruang 100000 angka yang hanya berisi 1000 angka. Dalam buku telepon yang sangat seragam, setiap angka akan lebih besar dari angka sebelumnya sebesar 100:

55555-12 3 45
55555-12 4 45
55555-12 5 45

Jika itu masalahnya, itu akan membutuhkan penyimpanan nol untuk menyandikan perbedaan antara angka, karena ini adalah konstanta yang diketahui. Sayangnya, angka mungkin berbeda dari langkah ideal 100. Saya akan menyandikan perbedaan dari kenaikan ideal 100, sehingga jika dua angka yang berdekatan berbeda 103, saya akan menyandikan angka 3 dan jika dua angka yang berdekatan berbeda 92, saya akan menyandikan -8. Saya menyebut delta dari kenaikan ideal 100 sebagai " varians ".

Varians dapat berkisar dari -99 (yaitu dua angka berurutan) hingga 99000 (seluruh buku telepon terdiri dari angka 00000… 00999 dan tambahan nomor terjauh 99999), yang merupakan kisaran 99100 nilai yang memungkinkan.

Saya akan bertujuan untuk mengalokasikan penyimpanan minimal untuk mengkodekan perbedaan yang paling umum dan memperluas penyimpanan jika saya menemukan perbedaan besar (seperti protobuf ‘s varint). Saya akan menggunakan potongan tujuh bit, enam untuk penyimpanan dan bit bendera tambahan di bagian akhir untuk menunjukkan bahwa varian ini disimpan dengan potongan tambahan setelah yang sekarang, hingga maksimum tiga potongan (yang akan memberikan maksimum 3 * 6 = 18 bit penyimpanan, yang merupakan 262144 nilai yang mungkin, lebih dari jumlah kemungkinan varians (99100). Setiap potongan tambahan yang mengikuti bendera yang ditinggikan memiliki bit dengan signifikansi lebih tinggi, sehingga potongan pertama selalu memiliki bit 0- 5, potongan kedua opsional memiliki bit 6-11, dan potongan ketiga opsional memiliki bit 12-17.

Satu potongan menyediakan enam bit penyimpanan yang dapat menampung 64 nilai. Saya ingin memetakan 64 varian terkecil agar sesuai dalam potongan tunggal itu (yaitu varian -32 hingga +31) jadi saya akan menggunakan pengkodean ProtoBuf ZigZag, hingga varian -99 hingga +98 (karena tidak perlu untuk varian negatif di luar -99), di mana saya akan beralih ke pengkodean biasa, diimbangi dengan 98:  

Varians | Nilai Tersandi
----------- + ----------------
    0 | 0
   -1 | 1
    1 | 2
   -2 | 3
    2 | 4
   -3 | 5
    3 | 6
   ... | ...
  -31 | 61
   31 | 62
  -32 | 63
----------- | --------------- 6 bit
   32 | 64
  -33 | 65
   33 | 66
   ... | ...
  -98 | 195
   98 | 196
  -99 | 197
----------- | --------------- Akhir ZigZag
   100 | 198
   101 | 199
   ... | ...
  3996 | 4094
  3997 | 4095
----------- | --------------- 12 bit
  3998 | 4096
  3999 | 4097
   ... | ...
 262045 | 262143
----------- | --------------- 18 bit

Beberapa contoh bagaimana varians akan dikodekan sebagai bit, termasuk bendera untuk menunjukkan potongan tambahan:

Varians | Bit yang Dikodekan
----------- + ----------------
     0 | 000000 0
     5 | 001010 0
    -8 | 001111 0
   -32 | 111111 0
    32 | 000000 1 000001 0
   -99 | 000101 1 000011 0
   177 | 010011 1 000100 0
 14444 | 001110 1 100011 1 000011 0

Jadi, tiga nomor pertama dari buku telepon contoh akan dikodekan sebagai aliran bit sebagai berikut:

BIN 000101001011001000100110010000011001 000110 1 010110 1 00001 0
PH # 55555-12345 55555-12448 55555-12491
POS 1 2 3

Skenario kasus terbaik , buku telepon tersebar agak seragam dan tidak ada dua nomor telepon yang memiliki varian lebih besar dari 32, sehingga akan menggunakan 7 bit per nomor ditambah 32 bit untuk nomor awal dengan total 32 + 7 * 999 = 7025 bit .
Skenario campuran , di mana varian nomor telepon 800 cocok dalam satu bagian (800 * 7 = 5600), 180 nomor masuk dalam dua bagian masing-masing (180 * 2 * 7 = 2520) dan 19 nomor cocok dalam tiga bagian masing-masing (20 * 3 * 7 = 399), ditambah 32 bit awal, totalnya 8551 bit .
Skenario kasus terburuk , 25 angka masuk dalam tiga potongan (25 * 3 * 7 = 525 bit) dan 974 angka yang tersisa masuk dalam dua potongan (974 * 2 * 7 = 13636 bit), ditambah 32 bit untuk angka pertama untuk grand jumlah dari14193 bit .

   Jumlah nomor yang disandikan |
 1 bagian | 2-bagian | 3-bagian | Bit total
--------- + ---------- + ---------- + ------------
   999 | 0 | 0 | 7025
   800 | 180 | 19 | 8551
    0 | 974 | 25 | 14193

Saya dapat melihat empat pengoptimalan tambahan yang dapat dilakukan untuk lebih mengurangi ruang yang diperlukan:

  1. Potongan ketiga tidak membutuhkan tujuh bit penuh, bisa jadi hanya lima bit dan tanpa bit flag.
  2. Mungkin ada langkah awal untuk menghitung ukuran terbaik untuk setiap potongan. Mungkin untuk buku telepon tertentu, akan lebih optimal jika potongan pertama memiliki 5 + 1 bit, yang kedua 7 + 1 dan yang ketiga 5 + 1. Itu selanjutnya akan mengurangi ukuran menjadi minimal 6 * 999 + 32 = 6026 bit, ditambah dua set tiga bit untuk menyimpan ukuran potongan 1 dan 2 (ukuran potongan 3 adalah sisa dari 16 bit yang diperlukan) untuk total dari 6032 bit!
  3. Pass awal yang sama dapat menghitung kenaikan yang diharapkan lebih baik daripada default 100. Mungkin ada buku telepon yang dimulai dari 55555-50000, dan karenanya memiliki setengah rentang angka sehingga kenaikan yang diharapkan harus 50. Atau mungkin ada non-linear distribusi (mungkin deviasi standar) dan beberapa kenaikan harapan optimal lainnya dapat digunakan. Ini akan mengurangi varian tipikal dan memungkinkan potongan pertama yang lebih kecil untuk digunakan.
  4. Analisis lebih lanjut dapat dilakukan pada langkah pertama untuk memungkinkan buku telepon dipartisi, dengan setiap partisi memiliki peningkatan yang diharapkan dan optimalisasi ukuran potongannya sendiri. Ini akan memungkinkan ukuran potongan pertama yang lebih kecil untuk bagian tertentu dari buku telepon yang sangat seragam (mengurangi jumlah bit yang dikonsumsi) dan ukuran potongan yang lebih besar untuk bagian yang tidak seragam (mengurangi jumlah bit yang terbuang pada tanda kelanjutan).

0

Pertanyaan sebenarnya adalah salah satu cara menyimpan nomor telepon lima digit.

Triknya adalah Anda memerlukan 17 bit untuk menyimpan kisaran angka dari 0..99.999. Tetapi menyimpan 17-bit pada batas kata 8-byte konvensional merepotkan. Itulah mengapa mereka menanyakan apakah Anda dapat melakukannya dalam waktu kurang dari 4k dengan tidak menggunakan integer 32-bit.

Pertanyaan: apakah semua kombinasi angka mungkin?

Karena sifat dari sistem telepon, mungkin terdapat kurang dari 65k kemungkinan kombinasi. Saya akan berasumsi bahwa ya karena kita berbicara tentang lima posisi terakhir di nomor telepon, yang bertentangan dengan kode area atau awalan pertukaran.

Pertanyaan: apakah daftar ini akan statis atau perlu mendukung pembaruan?

Jika statis , maka ketika tiba saatnya untuk mengisi database, hitung jumlah digit <50.000 dan jumlah digit> = 50.000. Alokasikan dua array dariuint16 panjang yang tepat: satu untuk bilangan bulat di bawah 50.000 dan satu untuk set yang lebih tinggi. Saat menyimpan bilangan bulat dalam larik yang lebih tinggi, kurangi 50.000 dan saat membaca bilangan bulat dari larik tersebut, tambahkan 50.000. Sekarang Anda telah menyimpan 1.000 bilangan bulat Anda dalam 2.000 kata 8-byte.

Membuat buku telepon akan membutuhkan dua input traversal, tetapi pencarian seharusnya dilakukan dalam waktu setengah, rata-rata, daripada jika menggunakan satu larik. Jika waktu pencarian sangat penting, Anda dapat menggunakan lebih banyak array untuk rentang yang lebih kecil tetapi saya pikir pada ukuran ini, kinerja Anda pasti akan menarik array dari memori dan 2k mungkin akan disimpan ke dalam cache CPU jika tidak mendaftarkan ruang pada apa pun yang Anda gunakan. hari.

Jika dinamis , alokasikan satu larik berisi 1000 atau lebih uint16, dan tambahkan angka dalam urutan yang diurutkan. Setel byte pertama menjadi 50.001, dan setel byte kedua ke nilai null yang sesuai, seperti NULL atau 65.000. Saat Anda menyimpan nomor, simpan dalam urutan yang diurutkan. Jika sebuah angka di bawah 50.001 maka simpanlah sebelum penanda 50.001. Jika suatu bilangan adalah 50.001 atau lebih besar, simpan setelah penanda 50.001, tetapi kurangi 50.000 dari nilai yang disimpan.

Array Anda akan terlihat seperti:

00001 = 00001
12345 = 12345
50001 = reserved
00001 = 50001
12345 = 62345
65000 = end-of-list

Jadi, saat Anda mencari nomor di buku telepon, Anda akan melintasi larik dan jika Anda telah mencapai nilai 50.001, Anda mulai menambahkan 50.000 ke nilai larik Anda.

Ini membuat sisipan menjadi sangat mahal, tetapi pencariannya mudah, dan Anda tidak akan menghabiskan lebih dari 2k untuk penyimpanan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.