Temukan "lubang" dalam daftar angka

14

Apa cara tercepat untuk menemukan bilangan bulat (terkecil) pertama yang tidak ada dalam daftar bilangan bulat yang tidak disortir (dan itu lebih besar dari nilai terkecil daftar)?

Pendekatan primitif saya adalah menyortir mereka dan masuk daftar, apakah ada cara yang lebih baik?

Fabian Zeindl
sumber
6
@Jodrell Saya pikir mengurutkan perkembangan yang tak terbatas akan sulit ;-)
maple_shaft
3
@maple_shaft setuju, bisa memakan waktu cukup lama.
Jodrell
4
Bagaimana Anda mendefinisikan pertama untuk daftar yang tidak disortir?
Jodrell
1
Saya baru menyadari ini mungkin milik StackOverflow, karena ini bukan masalah konseptual.
JasonTrue
2
@JasonTrue Dari FAQ, If you have a question about… •algorithm and data structure conceptsini tentang topik IMHO.
maple_shaft

Jawaban:

29

Dengan asumsi bahwa yang Anda maksud adalah "bilangan bulat" ketika Anda mengatakan "angka", Anda dapat menggunakan bitvector ukuran 2 ^ n, di mana n adalah jumlah elemen (katakanlah rentang Anda termasuk bilangan bulat antara 1 dan 256, maka Anda dapat menggunakan 256- bit, atau 32 byte, bitvector). Ketika Anda menemukan bilangan bulat di posisi n rentang Anda, atur bit ke-n.

Ketika Anda selesai menghitung koleksi bilangan bulat, Anda beralih pada bit di bitvector Anda, mencari posisi bit yang ditetapkan 0. Mereka sekarang cocok dengan posisi n dari bilangan bulat Anda yang hilang.

Ini adalah O (2 * N), oleh karena itu O (N) dan mungkin lebih hemat memori daripada menyortir seluruh daftar.

Jason True
sumber
6
Nah, sebagai perbandingan langsung, jika Anda memiliki semua bilangan bulat 32 bit positif yang tidak ditandatangani tetapi 1, Anda dapat menyelesaikan masalah bilangan bulat yang hilang dalam sekitar setengah gigabyte memori. Jika Anda mengurutkannya, Anda harus menggunakan lebih dari 8 gigabytes memori. Dan menyortir, kecuali dalam kasus khusus seperti ini (daftar Anda diurutkan setelah Anda memiliki bitvector) hampir selalu n log atau lebih buruk, jadi kecuali dalam kasus di mana konstanta melebihi kerumitan dalam biaya, pendekatan linier menang.
JasonTrue
1
Bagaimana jika Anda tidak tahu kisaran apriori?
Blrfl
2
Jika Anda memiliki tipe data integer, Blrfl, Anda tentu tahu batas maksimum rentang, bahkan jika Anda tidak memiliki informasi yang cukup untuk mempersempit lebih lanjut. Jika Anda tahu itu daftar kecil, tetapi tidak tahu ukuran pastinya, pengurutan mungkin solusi yang lebih sederhana.
JasonTrue
1
Atau lakukan loop lain terlebih dahulu melalui daftar untuk menemukan elemen terkecil dan terbesar. Kemudian Anda dapat mengalokasikan array ukuran yang tepat dengan nilai terkecil sebagai offset dasar. Masih O (N).
Aman
1
@JPatrick: Bukan pekerjaan rumah, bisnis, saya sudah lulus CS tahun lalu :).
Fabian Zeindl
4

Jika Anda mengurutkan seluruh daftar terlebih dahulu, maka Anda menjamin run-time terburuk. Selain itu, pilihan Anda untuk mengurutkan algoritma sangat penting.

Inilah cara saya mendekati masalah ini:

  1. Gunakan tumpukan , fokus pada elemen terkecil dalam daftar.
  2. Setelah setiap swap, lihat apakah Anda memiliki celah.
  3. Jika Anda menemukan celah, maka return: Anda telah menemukan jawaban Anda.
  4. Jika Anda tidak menemukan celah, lanjutkan bertukar.

Berikut ini visualisasi dari heap sort .

Jim G.
sumber
Satu pertanyaan, bagaimana Anda mengidentifikasi elemen "terkecil" dari daftar?
Jodrell
4

Hanya untuk menjadi esoteris dan "pintar", dalam kasus khusus array yang hanya memiliki satu "lubang", Anda dapat mencoba solusi berbasis XOR:

  • Tentukan rentang array Anda; ini dilakukan dengan mengatur variabel "max" dan "min" ke elemen pertama array, dan untuk setiap elemen setelah itu, jika elemen itu kurang dari min atau lebih besar dari max, atur min atau max ke nilai baru.
  • Jika kisaran satu kurang dari kardinalitas set, hanya ada satu "lubang" sehingga Anda dapat menggunakan XOR.
  • Inisialisasi variabel integer X ke nol.
  • Untuk setiap bilangan bulat dari min hingga maksimum secara inklusif, XOR menghargai dengan X dan menyimpan hasilnya dalam X.
  • Sekarang XOR masing-masing integer dalam array dengan X, menyimpan setiap hasil berturut-turut ke X seperti sebelumnya.
  • Setelah selesai, X akan menjadi nilai "lubang" Anda.

Ini akan berjalan kira-kira 2N waktu yang mirip dengan solusi bitvector, tetapi membutuhkan lebih sedikit ruang memori untuk setiap ukuran N> Namun, jika array memiliki beberapa "lubang", X akan menjadi "jumlah" XOR dari semua lubang, yang akan sulit atau tidak mungkin untuk dipisahkan ke dalam nilai lubang yang sebenarnya. Jika demikian, Anda kembali ke metode lain seperti pendekatan "pivot" atau "bitvector" dari jawaban lain.

Anda bisa mengulang ini juga menggunakan sesuatu yang mirip dengan metode pivot untuk lebih mengurangi kerumitan. Susun ulang susunan berdasarkan titik pivot (yang akan menjadi maks dari sisi kiri dan min kanan; akan sepele untuk menemukan maks dan min dari array penuh saat berputar). Jika sisi kiri pivot memiliki satu atau lebih lubang, rekur ulang hanya ke sisi itu; jika tidak berulang ke sisi lain. Di setiap titik di mana Anda dapat menentukan hanya ada satu lubang, gunakan metode XOR untuk menemukannya (yang secara keseluruhan harus lebih murah daripada terus berporos sampai ke kumpulan dua elemen dengan lubang yang dikenal, yang merupakan dasar kasus untuk algoritma pivot murni).

KeithS
sumber
Itu sangat pintar dan mengagumkan! Sekarang dapatkah Anda menemukan cara untuk melakukan ini dengan sejumlah lubang yang bervariasi? :-D
2

Berapa kisaran angka yang akan Anda temui? Jika rentang itu tidak terlalu besar, Anda bisa menyelesaikan ini dengan dua pemindaian (linear time O (n)) menggunakan array dengan elemen sebanyak jumlah angka, ruang perdagangan untuk waktu. Anda dapat menemukan rentang secara dinamis dengan satu pemindaian lagi. Untuk mengurangi ruang, Anda dapat menetapkan 1 bit untuk setiap angka, memberi Anda penyimpanan bernilai 8 angka per byte.

Pilihan Anda yang lain yang mungkin lebih baik untuk skenario awal dan akan menjadi insitu daripada menyalin memori adalah untuk memodifikasi jenis seleksi untuk berhenti lebih awal jika min ditemukan dalam pass pemindaian tidak 1 lebih dari min terakhir ditemukan.

Peter Smith
sumber
1

Tidak terlalu. Karena nomor yang belum dipindai selalu bisa menjadi nomor yang mengisi "lubang" tertentu, Anda tidak dapat menghindari pemindaian setiap nomor setidaknya sekali dan kemudian membandingkannya dengan kemungkinan tetangga. Anda mungkin dapat mempercepat beberapa hal dengan membangun pohon biner atau lebih dan kemudian membaliknya dari kiri ke kanan hingga lubang ditemukan, tetapi itu pada dasarnya memiliki kompleksitas waktu yang sama dengan penyortiran, karena penyortiran. Dan Anda mungkin tidak akan menghasilkan sesuatu yang lebih cepat dari Timsort .

pembuat pil
sumber
1
Apakah Anda mengatakan bahwa melintasi daftar adalah kompleksitas waktu yang sama dengan pengurutan?
maple_shaft
@maple_shaft: Tidak, saya katakan membangun pohon biner dari data acak dan kemudian menukarnya dari kiri ke kanan sama dengan menyortir dan kemudian berpindah dari kecil ke besar.
pembuat pil KB
1

Sebagian besar ide di sini tidak lebih dari sekadar penyortiran. Versi bitvector adalah Bucketsort biasa. Heap sort juga disebutkan. Ini pada dasarnya bermula untuk memilih algoritma pengurutan yang tepat yang tergantung pada persyaratan waktu / ruang dan juga pada kisaran dan jumlah elemen.

Dalam pandangan saya, menggunakan struktur tumpukan mungkin adalah solusi yang paling umum (tumpukan pada dasarnya memberi Anda elemen terkecil secara efisien tanpa jenis lengkap).

Anda juga bisa menganalisis pendekatan yang menemukan angka terkecil terlebih dahulu dan kemudian memindai setiap bilangan bulat lebih besar dari itu. Atau Anda menemukan 5 angka terkecil berharap akan ada celah.

Semua algoritma ini memiliki kekuatan mereka tergantung pada karakteristik input dan persyaratan program.

Gerenuk
sumber
0

Solusi yang tidak menggunakan penyimpanan tambahan atau menganggap lebar (32 bit) bilangan bulat.

  1. Dalam satu lintasan linear, temukan angka terkecil. Mari kita sebut ini "min". O (n) kompleksitas waktu.

  2. Pilih elemen pivot acak dan lakukan partisi gaya quicksort.

  3. Jika pivot berakhir di posisi = ("pivot" - "min"), kemudian muncul kembali di sisi kanan partisi, atau muncul lagi di sisi kiri partisi. Idenya di sini adalah bahwa jika tidak ada lubang dari awal, pivot akan berada pada posisi ("pivot" - "min"), sehingga lubang pertama harus terletak di sebelah kanan partisi dan sebaliknya.

  4. Basis kasus adalah array dari 1 elemen dan lubang terletak di antara elemen ini dan yang berikutnya.

Total kompleksitas waktu berjalan yang diharapkan adalah O (n) (8 * n dengan konstanta) dan kasus terburuk adalah O (n ^ 2). Analisis kompleksitas waktu untuk masalah serupa dapat ditemukan di sini .

ayah
sumber
0

Saya percaya saya telah datang dengan sesuatu yang harus bekerja secara umum dan efisien jika Anda dijamin tidak memiliki duplikat * (namun, itu harus diperluas ke sejumlah lubang dan berbagai bilangan bulat).

Gagasan di balik metode ini seperti quicksort, di mana kita menemukan pivot dan partisi di sekitarnya, kemudian muncul kembali pada sisi dengan lubang. Untuk melihat sisi mana yang memiliki lubang, kami menemukan angka terendah dan tertinggi, dan membandingkannya dengan pivot dan jumlah nilai di sisi itu. Katakanlah pivot adalah 17 dan angka minimum adalah 11. Jika tidak ada lubang, harus ada 6 angka (11, 12, 13, 14, 15, 16, 17). Jika ada 5, kita tahu ada lubang di sisi itu dan kita bisa melihat lagi di sisi itu untuk menemukannya. Saya kesulitan menjelaskannya lebih jelas dari itu, jadi mari kita ambil contoh.

15 21 10 13 18 16 22 23 24 20 17 11 25 12 14

Poros:

10 13 11 12 14 |15| 21 18 16 22 23 24 20 17 25

15 adalah pivot, ditunjukkan oleh pipa ( ||). Ada 5 angka di sisi kiri pivot, karena harus ada (15 - 10), dan 9 di kanan, di mana harus ada 10 (25 - 15). Jadi kita berulang di sisi kanan; kami akan mencatat bahwa batas sebelumnya adalah 15 jika lubang berdekatan (16).

[15] 18 16 17 20 |21| 22 23 24 25

Sekarang ada 4 angka di sisi kiri tetapi harus ada 5 (21 - 16). Jadi kita mengulanginya di sana, dan sekali lagi kita akan perhatikan ikatan sebelumnya (dalam kurung).

[15] 16 17 |18| 20 [21]

Sisi kiri memiliki 2 angka yang benar (18 - 16), tetapi kanan memiliki 1 bukan 2 (20 - 18). Bergantung pada kondisi akhir kami, kami dapat membandingkan 1 angka dengan dua sisi (18, 20) dan melihat bahwa 19 hilang atau berulang sekali lagi:

[18] |20| [21]

Sisi kiri memiliki ukuran nol, dengan celah antara pivot (20) dan ikatan sebelumnya (18), jadi 19 adalah lubangnya.

*: Jika ada duplikat, Anda mungkin bisa menggunakan hash set untuk menghapusnya dalam waktu O (N), menjaga metode keseluruhan O (N), tetapi itu mungkin membutuhkan waktu lebih lama daripada menggunakan beberapa metode lain.

Kevin
sumber
1
Saya tidak percaya OP mengatakan sesuatu tentang hanya ada satu lubang. Input adalah daftar nomor yang tidak disortir - bisa berupa apa saja. Dari uraian Anda tidak jelas bagaimana Anda menentukan berapa angka "seharusnya".
Caleb
@caleb Tidak peduli berapa banyak lubang yang ada, hanya saja tidak ada duplikat (yang dapat dihapus di O (N) dengan hash set, meskipun dalam praktiknya mungkin memiliki lebih banyak overhead daripada metode lain). Saya sudah mencoba meningkatkan deskripsi, lihat apakah ini lebih baik.
Kevin
Ini bukan linear, IMO. Ini lebih seperti (logN) ^ 2. Pada setiap langkah, Anda memutar subset koleksi yang Anda pedulikan (setengah dari subarray sebelumnya yang telah Anda identifikasi memiliki "lubang" pertama), kemudian muncul kembali ke kedua sisi kiri jika memiliki "lubang", atau sisi kanan jika sisi kiri tidak. (logN) ^ 2 masih lebih baik dari linear; jika N bertambah sepuluh kali lipat Anda hanya mengambil urutan 2 (log (N) -1) + 1 langkah lagi.
KeithS
@Keith - sayangnya, Anda harus melihat semua angka di setiap level untuk memutar mereka, sehingga akan membutuhkan waktu sekitar n + n / 2 + n / 4 + ... = 2n (secara teknis, 2 (nm)) perbandingan .
Kevin