Listede Olmayan En Küçük Tamsayıyı Bulun


87

Bir meslektaşımın kullandığı ilginç bir röportaj sorusu:

Size çok uzun, sıralanmamış 64 bitlik işaretsiz tam sayılar listesi verildiğini varsayalım. Listede bulunmayan en küçük negatif olmayan tamsayıyı nasıl bulursunuz ?

İZLEME: Sıralamayla bariz bir çözüm önerildiğine göre, bunu O (n log n) den daha hızlı yapabilir misiniz?

TAKİP: Algoritmanız, örneğin 1 GB belleğe sahip bir bilgisayarda çalışmalıdır.

AYDINLATMA: Liste RAM içindedir, ancak büyük bir kısmını tüketebilir. Önceden N diyelim listenin boyutu size verilir.


6
İşaretsiz bir tam sayıdan nasıl bahsettiğinizi görerek olumsuz olmayan kısmı atlayabilirsiniz.
KevenDenen

4
Bu soru oldukça basit, eğer temelden uzaklaşmadığım sürece, IMO, ancak diğerlerinin de bahsettiği gibi, sorulması gereken sorular veya belirtilmesi gereken varsayımlar var.
James Black

8
@paxdiablo: Bu, O (n) demenin o kadar da bir anlam ifade etmediği bir durum. 2 ^ 64 bit dizinizi Paskalya Adası'ndaki kil tabletlerde saklasanız ve ona taşıyıcı güvercinle erişseniz bile, algoritma hala O (n).
IJ Kennedy

6
Hafıza gereksinimlerini yarı yolda değiştirmek, bunu harika bir röportaj sorusu haline getiriyor ;-)
Chris Ballance

1
Bence tüm yanıtların aynı genel çözümü yapması (diziyi sıralayın ve diziyi bozan ilk değeri bulun), ancak hepsinin farklı bir tür kullanıyor olması. (Değiştirilmiş hızlı sıralama, taban sıralaması, ...) Kabul edilen yanıt, N'nin üzerindeki öğeleri atan bir sayma sıralamasına eşdeğerdir.
Joren

Yanıtlar:


121

Veri yapısı yerinde değiştirilebiliyorsa ve rastgele erişimi destekliyorsa, bunu O (N) zamanı ve O (1) ek alanda yapabilirsiniz. Sadece diziyi sırayla gözden geçirin ve her dizin için dizindeki değeri değerle belirtilen dizine yazın, o konuma herhangi bir değeri yinelemeli olarak yerleştirin ve değerleri atarak> N Sonra noktayı arayan diziye tekrar gidin burada değer dizinle eşleşmiyor - bu dizide olmayan en küçük değerdir. Bu, en fazla 3N karşılaştırmasına neden olur ve yalnızca birkaç değer değerinde geçici alan kullanır.

# Pass 1, move every value to the position of its value
for cursor in range(N):
    target = array[cursor]
    while target < N and target != array[target]:
        new_target = array[target]
        array[target] = target
        target = new_target

# Pass 2, find first location where the index doesn't match the value
for cursor in range(N):
    if array[cursor] != cursor:
        return cursor
return N

9
Küçük nitpick. Önemsiz bir vakayı kaçırdınız: liste {0, ..., N-1} olduğunda. Bu durumda, geçiş 1 hiçbir şey yapmaz ve 2 dizisinde [imleç] == listedeki tüm girişler için imleç, böylece algoritma geri dönmez. Yani sonunda bir 'return N' ifadesine ihtiyacınız var.
Alex

12
Çözümünüz, alanı ve aralığı birleştirir (hedef hem bir değer hem de bir dizindir). Aralık, mevcut depolama alanıyla 128M öğelerle sınırlıdır, ancak etki alanı 2G boyutundadır. Diziye tahsis edilebilecek girdi sayısından daha büyük bir değere sahip tek bir girişle başarısız olur. Soru 'çok uzun' belirtmediyse, girdiyi yok etse bile cevap zariftir. Zaman-uzay değiş tokuşu bu problemde çok belirgindir ve sağlanan kısıtlamalar altında bir O (N) çözümü mümkün olmayabilir.
Pekka

2
İkinci geçiş, doğrusal arama yerine ikili aramayı kullanabilir.
user448810

4
Bu çözüm yalnızca değerler aralığı ve dizin karşılaştırılabilirse işe yarar.
Dubby

7
Daha büyük değerlerle iyi çalışacaktır. Dizide olmayan en küçük değerle hiçbir ilgisi olmadığı için daha büyük değerler göz ardı edilebilir. Örneğiniz için, ilk geçiş, hedef <N nedeniyle tüm değerleri yok sayarak dizi üzerinde döngü oluşturacak ve ardından ikinci geçişin ilk yinelemesinde 0 döndürecektir.
Karıncalar Aasma

89

İşte alanı O(N)kullanan basit bir çözüm O(N). Giriş listesini negatif olmayan sayılarla sınırladığımızı ve listede olmayan ilk negatif olmayan sayıyı bulmak istediğimizi varsayıyorum.

  1. Listenin uzunluğunu bulun; diyelim öyle N.
  2. NHepsi için başlatılan bir boole dizisi ayırın false.
  3. XListedeki her sayı için, eğer Xküçükse N, X'thdizinin öğesini olarak ayarlayın true.
  4. Diziyi dizinden başlayarak 0ilk öğeyi arayarak tarayın false. İlkini falsedizinde bulursanız I, o zaman Icevaptır. Aksi takdirde (yani tüm unsurlar olduğu zaman true) cevaptır N.

Pratikte, " Nmantıksal değer dizisi " muhtemelen bir byteveya intdizi olarak temsil edilen "bit eşlem" veya "bit kümesi" olarak kodlanacaktır . Bu genellikle daha az alan kullanır (programlama diline bağlı olarak) ve ilk taramanın falsedaha hızlı yapılmasını sağlar.


Algoritmanın nasıl / neden çalıştığı budur.

NListedeki sayıların farklı olmadığını veya bir veya daha fazlasının değerinden büyük olduğunu varsayalım N. Bu , listede olmayan aralıkta en az bir sayı olması gerektiği anlamına gelir 0 .. N - 1. Bu nedenle, en küçük eksik sayıyı bulma sorunu, bu nedenle, en küçük eksik sayıyı daha azN bulma sorununa indirgemelidir . Bu, büyük veya eşit sayıları takip etmemize gerek olmadığı anlamına gelir N... çünkü cevap onlar olmayacaktır.

Önceki paragrafın alternatifi, listenin, gelen sayıların bir permütasyonu olmasıdır 0 .. N - 1. Bu durumda, 3. adım dizinin tüm öğelerini olarak ayarlar trueve 4. adım bize ilk "eksik" sayının olduğunu söyler N.


Algoritmanın hesaplama karmaşıklığı, O(N)nispeten küçük bir orantılılık sabiti ile. Listede iki doğrusal geçiş yapar veya liste uzunluğunun başladığı biliniyorsa yalnızca bir geçiş yapar. Listenin tamamını bellekte tutmaya gerek yoktur, bu nedenle algoritmanın asimtotik bellek kullanımı boole dizisini temsil etmek için gereken şeydir; yani O(N)bitler.

(Aksine, bellek içi sıralama veya bölümlemeye dayanan algoritmalar, tüm listeyi bellekte temsil edebileceğinizi varsayar. Sorunun sorulduğu biçimde, bu O(N)64 bit sözcükler gerektirir .)


@Jorn, 1'den 3'e kadar olan adımların sıralamayı saymanın bir varyasyonu olduğunu söylüyor. Bir bakıma haklı ama farklar önemli:

  • Sayma sıralaması , listedeki en büyük sayı ve listedeki en küçük sayı olan (en azından) bir Xmax - Xminsayaç dizisi gerektirir . Her sayaç N durumu temsil edebilmelidir; yani bir ikili gösterimi varsayarsak, tam sayı tipinde (en azından) bitlere sahip olmak zorundadır .XmaxXminceiling(log2(N))
  • Dizi boyutunu belirlemek için, bir sayma sıralaması Xmaxve belirlemek için listeden bir ilk geçiş yapması gerekir Xmin.
  • Bu nedenle minimum en kötü durum alanı gereksinimi ceiling(log2(N)) * (Xmax - Xmin)bittir.

Aksine, yukarıda sunulan algoritma basitçe Nen kötü ve en iyi durumlarda bit gerektirir .

Bununla birlikte, bu analiz, eğer algoritma bir sıfırı bulmak için listeden bir ilk geçiş yaparsa (ve gerekirse liste öğelerini sayarsa), sıfırı bulursa hiç boşluk kullanmadan daha hızlı bir cevap vereceği sezgisine yol açar. Listede en az bir sıfır bulma olasılığı yüksekse kesinlikle bunu yapmaya değer. Ve bu ekstra geçiş genel karmaşıklığı değiştirmez.


DÜZENLEME: Algoritmanın açıklamasını "boole dizisi" kullanacak şekilde değiştirdim çünkü insanlar görünüşe göre orijinal açıklamamı bitler ve bit eşlemler kullanarak kafa karıştırıcı buldular.


3
@ adi92 Adım 3 size tüm bitleri 1'e ayarlanmış bir bit eşlem verirse, liste 0'dan N-1'e kadar her değeri içerir. Bu, listedeki en küçük negatif olmayan tamsayı N olduğu anlamına gelir. 0 ile N-1 arasında listede OLMAYAN herhangi bir değer varsa, o zaman karşılık gelen bit ayarlanmayacaktır. Bu nedenle, böylesi en küçük değer cevaptır.
divegeek

4
@ adi92 Örneğinizde, liste 300 öğe içerecektir. Bu, herhangi bir "eksik" değer varsa, 300'den az olması gerektiği anlamına gelir. Algoritmayı çalıştırarak, 300 yuvalı bir bit alanı oluştururuz ve ardından bitleri 1, 2 ve 3 numaralı yuvalarda tekrar tekrar ayarlayıp tüm diğer yuvalar - 0 ve 4 ila 299 - temiz. Bit alanını tararken, 0 numaralı yuvadaki bayrağı net bulurduk, bu nedenle 0'ın yanıt olduğunu bilirdik.
divegeek

4
Bu algoritmanın biraz oynamadan daha basit bir şekilde anlaşılabileceğini unutmayın: "N boyutunda bir Boole dizisi oluşturun" vb. Bunu bu şekilde anladıktan sonra, bit tabanlı bir sürüme geçmek kavramsal olarak kolaydır.
Jon Skeet

2
Soyut bir çözüm verirken, kavramsal olarak işe yarayan en basit yolu kullanın ve aşırı uzmanlaşmayın. Çözümünüz bir (soyut) boole dizisinin kullanımı için çığlık atıyor, öyleyse öyle deyin. Bu diziyi bool[]bir bit eşlem ile uygulayabilmeniz genel çözümle ilgisizdir.
Joren

2
Bence bu çözüm en iyi "N'nin üzerindeki öğeleri göz ardı eden bir sayma sıralaması kullanın, ardından başlangıçtan itibaren doğrusal bir arama yaparak ilk eksik öğeyi bulun."
Joren

13

OP şimdi orijinal listenin RAM'de tutulduğunu ve bilgisayarın sadece 1GB belleğe sahip olduğunu belirttiğinden, bir uzvun dışına çıkıp cevabın sıfır olduğunu tahmin edeceğim.

1GB RAM, listede en fazla 134,217,728 numara olabileceği anlamına gelir. Ancak 2 64 = 18,446,744,073,709,551,616 olası sayı vardır. Yani listede sıfır olma olasılığı 137.438.953.472'de 1'dir.

Aksine, bu yıl yıldırım çarpma olasılığım 700.000'de 1. Ve bir göktaşı çarpma olasılığım 10 trilyonda 1. Bu yüzden, cevabın sıfır olmamasından ziyade gök cismi tarafından zamansız ölümüm nedeniyle bilimsel bir dergide yazılma olasılığım yaklaşık on kat daha fazladır.


11
Hesaplamanız yalnızca değerler tekdüze olarak dağıtılırsa ve rastgele seçilirse geçerlidir. Sıralı olarak da oluşturulmuş olabilirler.
divegeek

1
Tabii ki haklısın. Ama ben tamamen genel durum için optimize etmek üzereyim. :)
Barry Brown

10
Öyleyse, görüşülen kişinin bu cevapla seçilme olasılığı nedir?
Amarghosh

6
Soru, sayıların aynı şekilde rastgele seçildiğini söylemez. Bu soruyu soran kişi tarafından seçilirler. Buna göre, listede 0 olma olasılığı 137.438.953.472'de 1'den çok daha büyük, muhtemelen 2'de 1'den bile daha büyük :-)
ShreevatsaR

8
@Amarghosh Bu sorunun cevabı da sıfır.
PeterAllenWebb

10

Diğer cevaplarda belirtildiği gibi, bir sıralama yapabilir ve ardından bir boşluk bulana kadar basitçe tarayabilirsiniz.

Algoritmik karmaşıklığı O (N) olarak iyileştirebilir ve boşluğu içermek için potansiyel aday olmayan bölümleri ortadan kaldırdığınız değiştirilmiş bir QuickSort kullanarak O (N) alanını koruyabilirsiniz.

  • İlk bölüm aşamasında, kopyaları kaldırın.
  • Bölümleme tamamlandığında, alt bölümdeki öğelerin sayısına bakın.
  • Bu değer bölümü oluşturmak için kullanılan değere eşit mi?
    • Eğer öyleyse, o zaman boşluğun daha yüksek bölümde olduğu anlamına gelir.
      • Alt bölümü yok sayarak hızlı sıralamaya devam edin
    • Aksi takdirde boşluk alt kısımdadır
      • Daha yüksek bölümü yok sayarak hızlı sıralamaya devam edin

Bu, çok sayıda hesaplama tasarrufu sağlar.


Bu oldukça şık. Bölümün uzunluğunu doğrusal zamandan daha kısa sürede hesaplayabileceğinizi varsayar; bu, bölüm dizisi ile birlikte saklanırsa yapılabilir. Ayrıca orijinal listenin RAM'de tutulduğunu varsayar.
Barry Brown

2
Listenin uzunluğunu biliyorsanız, len (list) değerinden daha büyük herhangi bir değeri de ayırabilirsiniz. Güvercin deliği ilkesine göre, herhangi bir 'delik' len (liste) değerinden daha az olmalıdır.
divegeek

1
Bunun O (n) olduğunu sanmıyorum ... Birincisi, bir liste tamamen sıralanana kadar kopyaları kaldırabileceğinizden emin değilim. İkinci olarak, her yinelemede arama alanının yarısının atılmasını garanti ederken (orta noktanın altına ve üstüne böldüğünüz için), n'ye bağlı veriler üzerinde hala birden çok geçişiniz (n'ye bağlı olarak) vardır.
paxdiablo

1
paxdiablo: Stephen C'nin önerdiği gibi bir bitmap yöntemi kullanarak yalnızca benzersiz değerlere sahip yeni bir liste oluşturabilirsiniz. Bu O (n) zamanda ve uzayda çalışır. Bundan daha iyi yapılabilir mi emin değilim.
Nic

9

O(N)Düşünmenin tuzaklarından birini açıklamak için , burada boşluk O(N)kullanan bir algoritma var O(1).

for i in [0..2^64):
  if i not in list: return i

print "no 64-bit integers are missing"

1
Will haklı. Bu O (n) değildir çünkü burada gerçekte iki döngü vardır, ancak biri örtüktür. Bir değerin listede olup olmadığını belirlemek O (n) işlemidir ve bunu for döngüsünde n kez yaparsınız. Bu onu O (n ^ 2) yapar.
Nic

6
Nic, Will, bu O (n * N), burada n listenin boyutu ve N, alanın boyutudur (64bit tamsayı). N çok büyük bir sayı olsa da, yine de sabittir, bu nedenle biçimsel olarak sorunun karmaşıklığı belirtildiği gibi O (n) 'dir.
Karıncalar Aasma

1
Karıncalar, O (n N) olduğuna katılıyorum , ancak N sabit değil. Algoritma cevabı bulduğunda bittiği için, dış döngü boyunca tamamlanan yinelemelerin sayısı, kendisi listenin boyutuna bağlı olan cevaba eşittir. O halde, O (N n) bu durumda O (n ^ 2) 'dir.
Will Harris

12
N elemanlı bir listede bir sayı aramak açıkça O (N) 'dur. Bunu 2 ^ 64 kez yapıyoruz. Büyük olmasına rağmen 2 ^ 64 SABİT'tir. Bu nedenle algoritma, hala O (N) olan C * O (N) 'dir.
IJ Kennedy

3
Önceki ifademi geri almalıyım; en katı tanımıyla, bu işlem gerçekten O (n) 'dir.
Nic

8

Sayıların tümü 64 bit uzunluğunda olduğundan , bunlar üzerinde O (n) olan radix sıralama kullanabiliriz . Sıralayın, ardından aradığınızı bulana kadar tarayın.

en küçük sayı sıfır ise, bir boşluk bulana kadar ileriye doğru tarayın. En küçük sayı sıfır değilse, cevap sıfırdır.


Doğru, ancak bellek gereksinimleri radix sıralama için oldukça yoğun olabilir.
PeterAllenWebb

1
Radix sıralama çok büyük veri kümeleri için çalışmaz. Ancak bölümleme ve taban sıralaması işe yarayabilir.
DarthVader

5

Alanı verimli kullanan bir yöntem için ve tüm değerler farklıdır, bunu uzay O( k )ve zamanda yapabilirsiniz O( k*log(N)*N ). Alan verimlidir ve veri taşıma yoktur ve tüm işlemler temeldir (çıkarma ekleme).

  1. Ayarlamak U = N; L=0
  2. Önce kbölgelerdeki sayı uzayını bölümlere ayırın. Bunun gibi:
    • 0->(1/k)*(U-L) + L, 0->(2/k)*(U-L) + L, 0->(3/k)*(U-L) + L...0->(U-L) + L
  3. count{i}Her bölgede kaç sayı ( ) olduğunu bulun . ( N*kadımlar)
  4. hDolu olmayan ilk bölgeyi ( ) bulun . Bu demektir count{h} < upper_limit{h}. (k adımlar)
  5. Eğer h - count{h-1} = 1 size cevap var
  6. Ayarlamak U = count{h}; L = count{h-1}
  7. 2'ye git

bu, hashing kullanılarak geliştirilebilir (bu fikir Nic için teşekkürler).

  1. aynı
  2. Önce kbölgelerdeki sayı uzayını bölümlere ayırın. Bunun gibi:
    • L + (i/k)->L + (i+1/k)*(U-L)
  3. inc count{j} kullanma j = (number - L)/k (if L < number < U)
  4. ilk bölgeyi bul (hiçinde k öğesi olmayan )
  5. Eğer count{h} = 1 h Cevabınız
  6. Ayarlamak U = maximum value in region h L = minimum value in region h

Bu koşacak O(log(N)*N).


Bu cevabı gerçekten beğendim. Okuması biraz zordu, ama soruyu okuduğumda kafamdaki şeye çok benziyor.
Nic

ayrıca bir noktada Stephen C tarafından hazırlanan o bitmap çözümüne geçmek akıllıca olurduU-L < k
Egon

Bu, O (log (N) * N) değil, O (N) olarak çalışır. Cevabınız @cdiggins cevabının bir genellemesidir ve O (N) ile çalışır çünkü aralıktaki i için toplam (1 / k ** i (ceil (log_k (n)))) <= 2.
Lapinot

Her yinelemede O (N) sayılarından geçersiniz, O (log_k (N)) toplam yineleme alır. Dolayısıyla O (log_k (N) * N) == O (log (N) * N). Orijinal numaralar sıralanmaz / gruplanmaz ve hepsini gözden geçirmeniz gerekir.
Egon

Ancak orijinal listeyi k bölgeye (n / k boyutunda) böldüyseniz, tam olmayan ilk bölgeyi seçersiniz. Bu nedenle, bir sonraki yinelemede, yalnızca seçilen bölgeyi göz önünde bulundurmanız ve onu k yeni bölgeye (n / k ** 2 boyutunda) vb. Bölmeniz gerekir. Aslında her seferinde tüm listeyi yinelemiyorsunuz (yoksa bölümlemenin anlamı ?).
Lapinot

3

Onları sıralar ve sonra bir boşluk bulana kadar diziyi çalıştırırım (başlangıçtaki sıfır ile ilk sayı arasındaki boşluk dahil).

Algoritma açısından şöyle bir şey yapabilir:

def smallest_not_in_list(list):
    sort(list)
    if list[0] != 0:
        return 0
    for i = 1 to list.last:
        if list[i] != list[i-1] + 1:
            return list[i-1] + 1
    if list[list.last] == 2^64 - 1:
        assert ("No gaps")
    return list[list.last] + 1

Elbette, CPU grunt'tan çok daha fazla belleğiniz varsa, tüm olası 64 bitlik değerlerden bir bit maskesi oluşturabilir ve listedeki her sayı için bitleri ayarlayabilirsiniz. Sonra bu bit maskesindeki ilk 0 biti arayın. Bu, zaman açısından bir O (n) işlemine dönüştürür, ancak bellek gereksinimleri açısından oldukça pahalıdır :-)

Her sayıya en az bir kez bakmayı gerektirmeyen bir yol göremediğim için O (n) 'yi geliştirebileceğinizden şüpheliyim.

Bunun için algoritma şu satırlar boyunca olacaktır:

def smallest_not_in_list(list):
    bitmask = mask_make(2^64) // might take a while :-)
    mask_clear_all (bitmask)
    for i = 1 to list.last:
        mask_set (bitmask, list[i])
    for i = 0 to 2^64 - 1:
        if mask_is_clear (bitmask, i):
            return i
    assert ("No gaps")

Listede olmayan en küçük öğe olduğu için açıklamadan ilk öğeye 0'ı engelliyor gibi görünüyor. Ama bu yaptığım bir varsayım, yanılıyor olabilirim.
James Black

Düşüncelerim, sıralı sıra 4,5,6 ise, 0'ın listede olmayan en küçük olacağı yönündeydi.
paxdiablo

Bence 2, 3, 5, cevabın 4 olması gerekiyor, ama yanılıyor olabilirim.
James Black

OP tarafından cevaplanması gereken bir soru. Arama alanı "tüm 64-bit işaretsiz tam sayılar" mı yoksa "listedeki en düşük ve en yüksekler arasındaki tüm sayılar" mı?
paxdiablo

En kötü durumda en az bir kez bakmanız gerektiğine katılıyorum, belki zaten ikili ağaçta sıralanmamışsa.
James Black

2

Listeyi sıralayın, birinci ve ikinci öğelere bakın ve boşluk olana kadar yukarı çıkmaya başlayın.


Listede değil, nasıl tanımladığınıza bağlıdır.
James Black

@PeterAllenWebb - Olacak, ancak sayılar rastgele mi yoksa sıralı mı?
James Black

1

Gizli faktör oldukça büyük olmasına rağmen, O (n) zamanında ve O (1) ek boşlukta yapabilirsiniz. Bu, sorunu çözmenin pratik bir yolu değildir, ancak yine de ilginç olabilir.

Her işaretsiz 64 bit tam sayı için (artan sırada), hedef tamsayıyı bulana veya listenin sonuna ulaşana kadar listeyi yineleyin. Listenin sonuna ulaşırsanız, hedef tam sayı listede olmayan en küçük tamsayıdır. 64 bitlik tam sayıların sonuna ulaşırsanız, her 64 bitlik tam sayı listede yer alır.

İşte bir Python işlevi olarak:

def smallest_missing_uint64(source_list):
    the_answer = None

    target = 0L
    while target < 2L**64:

        target_found = False
        for item in source_list:
            if item == target:
                target_found = True

        if not target_found and the_answer is None:
            the_answer = target

        target += 1L

    return the_answer

Bu işlev, onu O (n) tutmak için kasıtlı olarak yetersizdir. Özellikle, yanıt bulunduktan sonra bile işlevin hedef tam sayıları kontrol etmeye devam ettiğini unutmayın. Fonksiyon cevap bulunur bulunmaz geri döndüyse, dış döngünün kaç kez çalıştığı, n ile bağlı olan cevabın boyutuna bağlı olacaktır. Bu değişiklik, çok daha hızlı olmasına rağmen çalışma süresini O (n ^ 2) yapar.


Doğru. O (1) uzayı ve O (n) zamanı olan algoritmalardan bazılarının pratikte bu soruyla ne kadar korkunç bir şekilde başarısız olması eğlenceli.
PeterAllenWebb

1

İlham kaynağım için egon, swilden ve Stephen C'ye teşekkürler. İlk olarak, hedef değerinin sınırlarını biliyoruz çünkü listenin boyutundan büyük olamaz. Ayrıca, 1 GB'lık bir liste en çok 134217728 (128 * 2 ^ 20) 64 bit tam sayı içerebilir.

Hashing parçası
Arama alanımızı önemli ölçüde azaltmak için hashing kullanmayı öneriyorum. İlk olarak, listenin boyutunun karekökünü alın. 1 GB'lık bir liste için bu N = 11,586'dır. N boyutunda bir tamsayı dizisi oluşturun. Listede yineleyin ve bulduğunuz her sayının karekökünü * karma değeriniz olarak alın. Hash tablonuzda, bu hash için sayacı artırın. Ardından, karma tablonuzda yineleyin. Maksimum boyutuna eşit olmayan bulduğunuz ilk kova, yeni arama alanınızı tanımlar.

Bitmap bölümü
Şimdi yeni arama alanınızın boyutuna eşit normal bir bit eşlemi oluşturun ve arama alanınızdaki her bir sayıyı bulduğunuzda bit eşlemi doldurarak kaynak listesinde tekrar yineleyin. Bitirdiğinizde, bit eşleminizdeki ilk ayarlanmayan bit size cevabınızı verecektir.

Bu, O (n) zamanda ve O (sqrt (n)) uzayda tamamlanacaktır.

(* Bunu çok daha verimli bir şekilde yapmak için bit kaydırma gibi bir şey kullanabilir ve yalnızca kova sayısını ve boyutunu buna göre değiştirebilirsiniz.)


1
Bellek ayak izini azaltmak için arama alanını Kök-N kovalarına bölme fikrini seviyorum, ancak listedeki kopyalar bu yöntemi bozacaktır. Düzeltilip düzeltilemeyeceğini merak ediyorum.
PeterAllenWebb

Haklısın, çift girişleri dikkate almayı ihmal ettim. Bunun çözümlenebileceğinden emin değilim.
Nic

1

Bir sayı listesinde yalnızca bir eksik sayı varsa, eksik sayıyı bulmanın en kolay yolu seriyi toplamak ve listedeki her bir değeri çıkarmaktır. Son değer, eksik sayıdır.


Evet. Bu başka bir klasik mülakat sorusudur.
PeterAllenWebb

1
Bundan daha da kolay olanı, listedeki sayıları birlikte XOR, aralıktaki sayıları birlikte XOR ve sonuçları birlikte XOR.
John Kurlak

1
 int i = 0;
            while ( i < Array.Length)
            {

                if (Array[i] == i + 1)
                {
                    i++;
                }

                if (i < Array.Length)
                {
                    if (Array[i] <= Array.Length)
                    {//SWap

                        int temp = Array[i];
                        int AnoTemp = Array[temp - 1];
                        Array[temp - 1] = temp;
                        Array[i] = AnoTemp;

                    }
                    else
                       i++;



                }
            }

            for (int j = 0; j < Array.Length; j++)
            {
                if (Array[j] > Array.Length)
                {
                    Console.WriteLine(j + 1);
                    j = Array.Length;
                }
                else
                    if (j == Array.Length - 1)
                        Console.WriteLine("Not Found !!");

            }
        }

1

Numaraları tutmak için bir karma tablo kullanabiliriz. Tüm sayılar tamamlandığında, 0'dan en düşük olanı bulana kadar bir sayaç çalıştırın. Makul derecede iyi bir hash, sabit zamanda hash ve depolanır ve sabit zamanda alır.

for every i in X         // One scan Θ(1)
   hashtable.put(i, i);  // O(1)

low = 0;

while (hashtable.get(i) <> null)   // at most n+1 times
   low++;

print low;

En kötü durum n, dizide elemanlar varsa ve {0, 1, ... n-1}bu durumda, cevap nhala onu koruyarak elde edilecektir O(n).


1

İşte Java ile yazılmış cevabım:

Temel Fikir: 1- Geri kalanını toplarken, aynı zamanda maksimum pozitif sayıyı alırken yinelenen pozitif, sıfır ve negatif sayıları atarak dizide döngü yapın ve benzersiz pozitif sayıları bir Haritada tutun.

2- Toplamı max * (max + 1) / 2 olarak hesaplayınız.

3- 1. ve 2. adımlarda hesaplanan toplamlar arasındaki farkı bulun

4- 1'den minimum [toplam fark, maks] 'a tekrar döngü yapın ve 1. adımda doldurulan haritada olmayan ilk sayıyı döndürün.

public static int solution(int[] A) {
    if (A == null || A.length == 0) {
        throw new IllegalArgumentException();
    }

    int sum = 0;
    Map<Integer, Boolean> uniqueNumbers = new HashMap<Integer, Boolean>();
    int max = A[0];
    for (int i = 0; i < A.length; i++) {
        if(A[i] < 0) {
            continue;
        }
        if(uniqueNumbers.get(A[i]) != null) {
            continue;
        }
        if (A[i] > max) {
            max = A[i];
        }
        uniqueNumbers.put(A[i], true);
        sum += A[i];
    }
    int completeSum = (max * (max + 1)) /  2;
    for(int j = 1; j <= Math.min((completeSum - sum), max); j++) {
        if(uniqueNumbers.get(j) == null) { //O(1)
            return j;
        }
    }
    //All negative case
    if(uniqueNumbers.isEmpty()) {
        return 1;
    }
    return 0;
}

0

Stephen C'nin akıllıca işaret ettiği gibi, yanıt dizinin uzunluğundan daha küçük bir sayı olmalıdır. Daha sonra cevabı ikili aramayla bulurdum. Bu, en kötü durumu optimize eder (böylece görüşmeci sizi 'eğer' patolojik bir senaryoda yakalayamaz). Bir röportajda, bunu en kötü durum için optimize etmek için yaptığınızı belirtin.

İkili aramayı kullanmanın yolu, aradığınız sayıyı dizinin her bir öğesinden çıkarmak ve negatif sonuçları kontrol etmektir.


0

"Sıfır tahmin" yaklaşımını beğendim. Sayılar rastgele olsaydı, sıfır olasılığı yüksektir. "İnceleyen kişi" rastgele olmayan bir liste oluşturduysa, bir tane ekleyin ve tekrar tahmin edin:

LowNum=0
i=0
do forever {
  if i == N then leave /* Processed entire array */
  if array[i] == LowNum {
     LowNum++
     i=0
     }
   else {
     i++
   }
}
display LowNum

En kötü durum, n = N ile n * N'dir, ancak pratikte n'nin küçük bir sayı olma olasılığı yüksektir (örneğin 1)


0

Soruyu anladığımdan emin değilim. Ancak 1,2,3,5,6 listesi için ve eksik sayı 4 ise, eksik sayı O (n) 'de şu şekilde bulunabilir: (n + 2) (n + 1) / 2- (n + 1) n / 2

DÜZENLEME: üzgünüm, sanırım dün gece çok hızlı düşünüyordum. Her neyse, ikinci bölüm aslında O (n) 'nin geldiği yer olan sum (liste) ile değiştirilmelidir. Formül, arkasındaki fikri ortaya koymaktadır: n sıralı tam sayı için, toplam (n + 1) * n / 2 olmalıdır. Eksik bir sayı varsa, toplam, (n + 1) sıralı tamsayıların toplamından eksik sayının çıkarılmasıyla elde edilen sayıya eşit olacaktır.

Aklımda bazı orta parçalar koyduğuma işaret ettiğiniz için teşekkürler.


1
Bunun nasıl çalışacağını ilk bakışta görmüyorum. Sizin durumunuzda n = 5 ve formlera, içinde eksik olan sayı ne olursa olsun sabitlenecektir.
sisve

Simon: Şimdi lütfen benim düzenlememe göre olumsuz oyu kaldırır mısınız?
Codism

0

Tebrikler Karıncalar Aasma! Cevabı yaklaşık 15 dakika düşündüm ve bağımsız olarak, sizinkine benzer bir düşünce tarzında bir cevap buldum:

#define SWAP(x,y) { numerictype_t tmp = x; x = y; y = tmp; }
int minNonNegativeNotInArr (numerictype_t * a, size_t n) {
    int m = n;
    for (int i = 0; i < m;) {
        if (a[i] >= m || a[i] < i || a[i] == a[a[i]]) {
            m--;
            SWAP (a[i], a[m]);
            continue;
        }
        if (a[i] > i) {
            SWAP (a[i], a[a[i]]);
            continue;
        }
        i++;
    }
    return m;
}

m, "ilk i girişleri hakkında bildiklerim verildiğinde ve m-1'deki girişe kadar değerler hakkında başka hiçbir şey varsaymadığım mevcut maksimum olası çıktıyı" temsil eder.

Bu m değeri yalnızca (a [i], ..., a [m-1]) değerlerin (i, ..., m-1) bir permütasyonu ise döndürülür. Dolayısıyla, a [i]> = m veya a [i] <i ise veya a [i] == a [a [i]] ise, m'nin yanlış çıktı olduğunu ve en az bir eleman daha düşük olması gerektiğini biliyoruz. Yani m'yi azaltarak ve a [i] 'yi a [m] ile değiştirebiliriz.

Bu doğru değilse ancak a [i]> i ise, a [i]! = A [a [i]] olduğunu bildiğimizde, a [i] 'yi bir [a [i]] ile değiştirmenin eleman sayısını artıracağını biliyoruz kendi yerlerinde.

Aksi takdirde a [i] i'ye eşit olmalıdır, bu durumda bu indeks dahil tüm değerlerin indekslerine eşit olduğunu bilerek artırabiliriz.

Bunun sonsuz bir döngüye giremeyeceğinin kanıtı, alıştırma olarak okuyucuya bırakılmıştır. :)


0

Dafny Ants' cevabı şovları parçası neden yerinde algoritması başarısız olabilir. requiresÖn şart her öğenin değerleri dizi sınırlarının ötesine gerektiğini açıklamaktadır.

method AntsAasma(A: array<int>) returns (M: int)
  requires A != null && forall N :: 0 <= N < A.Length ==> 0 <= A[N] < A.Length;
  modifies A; 
{
  // Pass 1, move every value to the position of its value
  var N := A.Length;
  var cursor := 0;
  while (cursor < N)
  {
    var target := A[cursor];
    while (0 <= target < N && target != A[target])
    {
        var new_target := A[target];
        A[target] := target;
        target := new_target;
    }
    cursor := cursor + 1;
  }

  // Pass 2, find first location where the index doesn't match the value
  cursor := 0;
  while (cursor < N)
  {
    if (A[cursor] != cursor)
    {
      return cursor;
    }
    cursor := cursor + 1;
  }
  return N;
}

forall ...Doğrulama hatasını görmek için kodu doğrulayıcıya cümle ile ve cümle olmadan yapıştırın . İkinci hata, doğrulayıcının Geçiş 1 döngüsü için bir sonlandırma koşulu kuramamasının bir sonucudur. Bunu kanıtlamak, aracı daha iyi anlayan birine bırakılmıştır.


0

Java'da, girişi değiştirmeyen ve O (N) zaman ve N bit artı küçük bir sabit bellek yükü kullanan bir cevap var (burada N, listenin boyutudur):

int smallestMissingValue(List<Integer> values) {
    BitSet bitset = new BitSet(values.size() + 1);
    for (int i : values) {
        if (i >= 0 && i <= values.size()) {
            bitset.set(i);
        }
    }
    return bitset.nextClearBit(0);
}

0
def solution(A):

index = 0
target = []
A = [x for x in A if x >=0]

if len(A) ==0:
    return 1

maxi = max(A)
if maxi <= len(A):
    maxi = len(A)

target = ['X' for x in range(maxi+1)]
for number in A:
    target[number]= number

count = 1
while count < maxi+1:
    if target[count] == 'X':
        return count
    count +=1
return target[count-1] + 1

Yukarıdaki çözüm için% 100 var.


0

1) Negatif ve Sıfırı Filtrele

2) Sırala / farklı

3) Diziyi ziyaret edin

Karmaşıklık : O (N) veya O (N * log (N))

Java8 kullanarak

public int solution(int[] A) {
            int result = 1;
    boolean found = false;
    A = Arrays.stream(A).filter(x -> x > 0).sorted().distinct().toArray();
    //System.out.println(Arrays.toString(A));
    for (int i = 0; i < A.length; i++) {
        result = i + 1;
        if (result != A[i]) {
            found = true;
            break;
        }
    }
    if (!found && result == A.length) {
        //result is larger than max element in array
        result++;
    }
    return result;
}

0

Tüm pozitif sayıları saklamak için bir unordered_set kullanılabilir ve daha sonra 1'den sırasız_set uzunluğuna kadar yineleyebilir ve oluşmayan ilk sayıyı görebiliriz.

int firstMissingPositive(vector<int>& nums) {

    unordered_set<int> fre;
    // storing each positive number in a hash.
    for(int i = 0; i < nums.size(); i +=1)
    {
        if(nums[i] > 0)
            fre.insert(nums[i]);
     }

    int i = 1;
    // Iterating from 1 to size of the set and checking 
    // for the occurrence of 'i'

    for(auto it = fre.begin(); it != fre.end(); ++it)
    {
        if(fre.find(i) == fre.end())
            return i;
        i +=1;
    }

    return i;
}

0

Temel JavaScript aracılığıyla çözüm

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))

Umarım bu birine yardımcı olur.


0

Python ile en verimli değil, doğru

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import datetime

# write your code in Python 3.6

def solution(A):
    MIN = 0
    MAX = 1000000
    possible_results = range(MIN, MAX)

    for i in possible_results:
        next_value = (i + 1)
        if next_value not in A:
            return next_value
    return 1

test_case_0 = [2, 2, 2]
test_case_1 = [1, 3, 44, 55, 6, 0, 3, 8]
test_case_2 = [-1, -22]
test_case_3 = [x for x in range(-10000, 10000)]
test_case_4 = [x for x in range(0, 100)] + [x for x in range(102, 200)]
test_case_5 = [4, 5, 6]
print("---")
a = datetime.datetime.now()
print(solution(test_case_0))
print(solution(test_case_1))
print(solution(test_case_2))
print(solution(test_case_3))
print(solution(test_case_4))
print(solution(test_case_5))

0
def solution(A):
    A.sort()
    j = 1
    for i, elem in enumerate(A):
        if j < elem:
            break
        elif j == elem:
            j += 1
            continue
        else:
            continue
    return j

0

bu yardımcı olabilir:

0- A is [5, 3, 2, 7];
1- Define B With Length = A.Length;                            (O(1))
2- initialize B Cells With 1;                                  (O(n))
3- For Each Item In A:
        if (B.Length <= item) then B[Item] = -1                (O(n))
4- The answer is smallest index in B such that B[index] != -1  (O(n))

Bu Stephen C'nin cevabından farklı mı ? Nasıl?
gri sakal
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.