Sonsuz bir listeden en yüksek 100 numarayı alın


53

Arkadaşımdan birine bu röportaj sorusu soruldu -

“Herhangi bir zamanda ilk en yüksek 100 sayıyı döndürmek için bir veri yapısını korumanız gereken sonsuz sayıdaki sayılar listesinden gelen sabit bir sayı akışı var.

Bu basittir, sıralanmış bir listeyi azalan düzende tutmanız ve bu listedeki en düşük numarada bir iz bırakmanız gerekir. Elde edilen yeni numara bu en düşük sayıdan büyükse, o en düşük numarayı kaldırmanız ve yeni numarayı gerektiği gibi sıralanmış listeye eklemeniz gerekir.

Sonra soru uzatıldı -

“Ekleme siparişinin O (1) olduğundan emin olabilir misiniz? Bu mümkün mü?”

Bildiğim kadarıyla, herhangi bir sıralama algoritması kullanarak tekrar listelemek ve sıralamak için yeni bir numara ekleseniz bile, en iyi şekilde quicksort için O (logn) olur (sanırım). Yani arkadaşım mümkün olmadığını söyledi. Ancak ikna olmadı, listeden başka herhangi bir veri yapısını sürdürmesini istedi.

Dengeli İkili ağaç düşündüm, ama orada bile 1 sıralaması ile ekleme alamayacaksınız. Yukarıdaki problem için 1 Sırasına eklenebilecek herhangi bir veri yapısının olup olmadığını bilmek istedi ya da hiç mümkün değil.


19
Belki de bu sadece soruyu yanlış anlayan benim, ama neden sıralı bir liste tutmaya ihtiyacınız var ? Neden sadece en düşük sayıyı takip etmiyorsunuz ve bu sayıdan daha yüksek bir sayı ile karşılaşıyorsanız, en düşük sayıyı kaldırın ve listeyi sıraya koymadan yeni numarayı girin. Bu size O (1) verirdi.
EdoDodo

36
@EdoDodo - ve bu işlemden sonra, en düşük sayının ne olduğunu nereden biliyorsunuz?
Damien_The_Unbeliever

19
[O (100 * log (100)) = O (1)] listesini sıralayın veya yeni en düşük sayıyı almak için minimum [O (100) = O (1)] için doğrusal bir arama yapın. Listeniz sabit bir boyutta olduğundan, bu işlemlerin tümü de aynı zamandır.
Random832

6
Tüm listeyi sıralı tutmak zorunda değilsiniz. En yüksek veya en yüksek 2. sayının ne olduğu umrunda değil. Sadece en düşük olanın ne olduğunu bilmen gerekiyor. Yeni bir numara girdikten sonra, sadece 100 sayıyı geçiyorsunuz ve hangisinin en düşük olduğunu görüyorsunuz. Bu sabit zaman.
Tom Zych

27
Asimptotik bir işlem sırası sadece ilginç sorunun büyüklüğü bağlı olmaksızın büyüyebilir zaman. Sorunuzdan, hangi miktarın sınırsız büyümekte olduğu çok açık değil; 100'de sınırlanmış bir problem için asimptotik düzenin ne olduğunu sormuş gibisiniz; sorması gereken mantıklı bir soru bile değil; bir şey sınırsız büyümek zorunda. Eğer soru "ilk 100'ü değil, ilk O'yu (1) zamanında tutmak için yapabilir misin?" o zaman soru mantıklı.
Eric Lippert

Yanıtlar:


35

Diyelim ki k bilmek istediğiniz en yüksek sayıların sayısıdır (örneğinizde 100). Sonra O(k)da içinde olduğu yeni bir numara ekleyebilirsiniz O(1). Çünkü O(k*g) = O(g) if k is not zero and constant.


6
O (50) O (n), O (1) değil. O uzunluk N bir liste halinde takma (1) zaman o zaman 100 olursa anlamına N değerine bağlı anlamına gelir 10000, 50, 5000 olmamalı

18
@ Hamstergene - ama bu soruda, Nsıralanan listenin büyüklüğü veya şu ana kadar işlenmiş olan öğelerin sayısı mı? 10000 öğeyi işlerseniz ve ilk 100 öğeyi bir listede tutarsanız veya 1000000000 öğeyi işlerseniz ve ilk 100 öğeyi sıralanmış bir listede tutarsanız, o listedeki ekleme maliyetleri aynı kalır.
Damien_The_Unbeliever

6
@ hamstergene: Bu durumda temelleri yanlış anladınız. Senin wikipedia linkte ( "sabit tarafından Çarpma") bir özellik vardır: O(k*g) = O(g) if k not zero and constant. => O(50*1) = O(1).
duedl0r

9
Bence duedl haklı. Sorunu azaltalım ve sadece minimum ve maksimum değerlere ihtiyacınız olduğunu varsayalım. Bu O (n) çünkü minimum ve maksimum 2? (n = 2). No. 2, sorunun tanımının bir parçasıdır. Bir sabittir, bu yüzden O (k * bir şey) 'e eşdeğer olan O (bir şey)
xanatos

9
@ hamstergene: hangi fonksiyondan bahsediyorsun? değeri 100 bana oldukça sabit görünüyor ..
duedl0r

19

Liste sıralanmamış olsun. Yeni bir numara girip girmeyeceğinizi bulmak daha uzun zaman alacaktır, ancak ekleme O (1) olacaktır.


7
Başka bir şey olmazsa , bunun size akıllı aleck ödülünü vereceğini düşünüyorum . * 8 ')
Mark Booth

4
@Emilio, teknik olarak haklısın - ve elbette bu en doğru tür…
Gareth

1
Ancak, 100 numaranızın en düşük değerlerini de tutabilirsiniz, ardından O (1) harfine girip girmeme konusunda karar verebilirsiniz. O zaman sadece bir numara girdiğinizde, yeni en düşük numarayı aramanız gerekir. Fakat bu, her yeni sayı için olan, ekleyip eklememeye karar vermekten daha nadir görülür.
Andrei Vajna II

12

Bu kolay. Sabit listesinin boyutu, bu nedenle listenin sıralama süresi sabittir. Sabit sürede yürütülen bir işlemin O (1) olduğu söylenir. Bu nedenle listeyi düzenlemek sabit büyüklükteki bir liste için O (1) şeklindedir.


9

100 numarayı geçtikten sonra, bir sonraki numara için tahakkuk edecek maksimum maliyet, numaranın en yüksek 100 numarada olup olmadığını kontrol etmenin maliyetidir (bu CheckTime etiketini koyalım ) ve bu sete girme ve çıkarma düşük bir (en diyelim bu EnterTime ) (en azından sınırlı numaraları için) sürekli bir zaman, ya da o (1) .

Worst = CheckTime + EnterTime

Sonra, sayıların dağılımı rastgele ise, ortalama maliyet sahip olduğunuz sayıyı azaltır. Örneğin, 101'inci sayıyı maksimum sayıya girme şansınız 100/101, 1000'inci sayı için şans 1 / 10'du ve nt sayı için şansı 100 / n olacaktır. Dolayısıyla, ortalama maliyet için denklemimiz:

Average = CheckTime + EnterTime / n

Bu nedenle, n sonsuzluğa yaklaştığından, yalnızca CheckTime önemlidir:

Average = CheckTime

Sayılar bağlıysa, CheckTime sabittir ve dolayısıyla O (1) zamanıdır.

Rakamlar bağlı değilse, kontrol süresi daha fazla rakamla artacaktır. Teorik olarak, bu, eğer maksimum kümedeki en küçük sayı yeterince büyük olursa, kontrol süreniz daha büyük olacağından, daha fazla bit düşünmeniz gerekecektir. Bu, sabit zamandan biraz daha yüksek olacak gibi görünmesini sağlar. Bununla birlikte, bir sonraki sayının en yüksek sette olma şansının n sonsuzluğa yaklaştıkça sıfıra yaklaştığını ve bu nedenle daha fazla bit düşünmek için şansın 0'a da yaklaşacağını iddia edebilirsiniz, bu da O için bir tartışma olacaktır (1). saati.

Olumlu değilim, ama bağırsaklarım O (log (log (n))) zamanı olduğunu söylüyor . Bunun nedeni, en düşük sayının artması ihtimalinin logaritmik olması ve her kontrol için göz önünde bulundurmanız gereken bit sayısının da logaritmik olmasıdır. Bunu yapan diğer insanlarla ilgileniyorum çünkü gerçekten emin değilim ...


Listenin keyfi olması haricinde, artan sayıların listesi varsa?
dan_waterworth

@ dan_waterworth: Sonsuz liste isteğe bağlıysa ve sadece artmaya başlarsa (olasılık 1 / ∞ olur), bu CheckTime + EnterTimeher sayı için en kötü senaryoya uyar . Sayılar sınırsız, ve eğer öyleyse bu sadece mantıklı CheckTimeve EnterTimehem artış, sayıların büyüklüğü artışına en az logaritmik olacak.
Briguy37

1
Sayılar rastgele değil, keyfi var. Olasılıklar hakkında konuşmak hiç mantıklı değil.
dan_waterworth

@ dan_waterworth: Sayıların keyfi olduğunu şimdi iki kere söyledin. bunu nereden alıyorsun? Ayrıca, rastgele durumdan başlayarak rasgele sayılara istatistik uygulayabileceğinizi ve hakem hakkında daha fazla bilgi sahibi olduğunuzda doğruluğunu artırabileceğinizi düşünüyorum. Eğer söz sahibi olsaydı Örneğin, o hakemi sürekli artan sayıları diyelim ki, eğer daha ben oldu seçilmesi daha büyük bir şans olacağını görünür;)
Briguy37

7

İkili Yığın Ağaçları'nı biliyorsanız bu kolaydır . İkili yığınlar ortalama sabit zamanda, O (1) yerleştirmeyi destekler. Ve ilk x elemente kolayca erişmenizi sağlar.


İhtiyacınız olmayan öğeleri neden saklayın? (Çok düşük değerler) Özel bir algoritma gibi görünüyor daha uygundur. En düşük değerlerden daha yüksek olmadıklarında değerleri 'ekleyemezsiniz' dememek.
Steven Jeuris

Bilmiyorum, sezgim bana bir yığının (bazı lezzetlerin) bunu çok iyi çekebileceğini söylüyor. Bu, onun için tüm unsurları elinde tutması gerektiği anlamına gelmez. Araştırma yapmadım ama “doğru geliyor” (TM).
Rig

3
Bir miktar, mth seviyesinin altındaki bir şeyi atmak için bir yığın değiştirilebilir (ikili yığınlar için ve k = 100, m sayısı 7 olacaktır, çünkü düğüm sayısı = 2 ^ m-1'dir). Bu onu yavaşlatırdı, ama yine de sabit bir sürede itfaya alınacaktı.
Plutor

3
İkili bir min-öbek kullandıysanız (o zaman en üst minimumdur, her zaman kontrol ediyorsunuzdur) ve yeni bir sayı> min bulursanız, o zaman yeni bir tane ekleyebilmeniz için önce üst öğeyi çıkarmanız gerekir. . Üstteki (min) elemanın çıkarılması O (logN) olacaktır, çünkü ağacın her seviyesini bir kez geçmeniz gerekir. Bu yüzden, sadece uçların ortalama O (1) olduğu teknik olarak doğrudur, çünkü pratikte her sayı> min bulduğunuzda hala O (logN).
Scott Whitlock

1
@Plutor, ikili yığınların size vermeyeceği konusunda bazı garantiler alıyorsunuz. İkili bir ağaç olarak görselleştirerek, sol daldaki her bir öğenin sağ daldaki herhangi bir öğeden daha küçük olduğu durum olabilir, ancak en küçük öğelerin köke en yakın olduğunu varsayıyorsunuz.
Peter Taylor

6

Eğer görüşmeci gerçekten “her gelen numaranın sürekli bir zamanda işlendiğinden emin olabilir miyiz” sorusunu sormaya niyetliysa, o zaman çoktan çoktan belirtildiği gibi (örneğin, @ duedl0r'nin cevabına bakınız) arkadaşınızın çözümü zaten O (1) ve sıralanmamış liste kullanmış, kabarcık sıralamasını veya başka bir şeyi kullanmış olsa bile öyle olurdu. Bu durumda, soru zor bir soru olmadıkça veya yanlış hatırlamadığınız sürece pek bir anlam ifade etmiyor.

Anketörün sorusunun anlamlı olduğunu, O'nun (1) nasıl bir şey yapılacağını sormadığını, bunun çok açık bir şekilde olduğunu sanıyorum.

Çünkü sorgulama algoritması karmaşıklığı yalnızca girdi büyüklüğü süresiz olarak büyüdüğü zaman anlamlıdır ve burada büyüyebilecek tek girdi 100'dür; liste büyüklüğü; Asıl sorunun “Top N harcayarak O (1) zaman başına harcayacağımızdan emin olabilir miyiz? (Arkadaşınızın çözümünde olduğu gibi O (N) değil) mümkün mü?”.

Akla gelen ilk şey, Top-N-problemi için O (m) alanını kullanmak için O-m problemi için sayı başına O (1) zamanın karmaşıklığını alacak olan sıralama sayımıdır; burada m , gelen sayıların aralığının uzunluğudur. . Yani evet, mümkün.


4

Sabit ekleme süresine sahip olan bir Fibonacci yığınıyla uygulanan minimum öncelikli bir sıra kullanın :

1. Insert first 100 elements into PQ
2. loop forever
       n = getNextNumber();
       if n > PQ.findMin() then
           PQ.deleteMin()
           PQ.insert(n)

4
"Operasyon silip asgari işi silmek O(log n)itfa edilmiş zaman" , bu nedenle bu yine neden olacaktır O(log k)nerede kdeposuna öğe miktarıdır.
Steven Jeuris

1
Silme min O (log n) 'de çalıştığı için ( Wikipedia'ya göre ) bu, Emilio'nun "akıllı aleck ödülü" olarak adlandırılan cevabından farklı değil .
Nicole,

@Renoz Emilio'un cevabı minimum bulmak için O (k), benimki O (log k)
Gabe Moothart

1
@Gabe Yeterince adil, sadece prensip olarak demek istiyorum. Başka bir deyişle, 100'ü sabit olarak kabul etmezseniz, bu cevap da sabit bir zaman değildir.
Nicole,

@Renesis (Yanlış) deyimini yanıttan kaldırdım.
Gabe Moothart

2

Görev, istenen sayı listesinin N uzunluğunda O (1) olan bir algoritma bulmaktır. Bu yüzden ilk 100 sayı veya 10000 sayıya ihtiyacınız olursa, ekleme süresi O (1) olmalıdır.

Buradaki hile, listenin eklenmesi için O (1) gereksiniminden söz edilmesine rağmen, sorunun tüm sayı alanındaki arama süresiyle ilgili bir şey söylemediği, ancak bunun O (1) olabileceği ortaya çıktı. yanı sıra. Bu durumda çözüm aşağıdaki gibidir:

  1. Anahtarlar için sayıları olan ve değerler için bağlı liste işaretçilerinin çiftlerini içeren bir karma tablo düzenleyin. Her bir işaretçi çifti, bağlı bir liste dizisinin başlangıcı ve bitişidir. Bu normalde sadece bir eleman, sonra diğeri olacak. Bağlantılı listedeki her öğe, bir sonraki en yüksek sayıya sahip olan öğenin yanına gider. Bağlantılı liste böylece gerekli sayıların sıralanmış sırasını içerir. En düşük sayının kaydını tutun.

  2. Rasgele akıştan yeni bir sayı x alın.

  3. En son kaydedilen en düşük sayıdan daha mı yüksek? Evet => Adım 4, Hayır => Adım 2

  4. Yeni alınan numara ile hash tablosuna basın. Bir giriş var mı? Evet => Adım 5. Hayır => Yeni bir x-1 numarası alın ve bu adımı tekrarlayın (bu basit bir aşağı doğru doğrusal arama, sadece burada yanımda kalın, bu geliştirilebilir ve nasıl açıklayacağım)

  5. Karma öğesinden yeni elde edilen liste öğesiyle, yeni sayıyı, bağlantılı listedeki öğenin hemen arkasına yazın (ve karma değerini güncelleyin).

  6. Kaydedilen en düşük l sayısını alın (ve karma / listeden çıkarın).

  7. Yeni alınan numara ile hash tablosuna basın. Bir giriş var mı? Evet => Adım 8. Hayır => Yeni bir l + 1 numarası alın ve bu adımı tekrarlayın (bu basit bir yukarı doğru doğrusal aramadır)

  8. Olumlu bir vuruşla sayı yeni en düşük sayı olur. 2. adıma gidin

Yinelenen değerlere izin vermek için karma gerçekte yinelenen öğelerin bağlantılı liste dizisinin başlangıcını ve sonunu koruması gerekir. Belirli bir tuşa eleman eklenmesi veya çıkarılması, böylece işaret edilen aralığı arttırır veya azaltır.

Buradaki insert O (1). Bahsedilen aramalar, sanırım, O (sayılar arasındaki ortalama fark) gibi bir şey. Ortalama fark, sayı alanının boyutuyla birlikte artar, ancak sayı listesinin istenen uzunluğu ile azalır.

Bu nedenle, eğer sayı alanı büyükse (ör. 4 baytlık bir int tipi için, 0 ila 2 ^ 32-1) ve N = 100 ise, doğrusal arama stratejisi oldukça zayıftır. Bu performans sorununu çözmek için, uygun anahtarlar yapmak için sayıların daha yüksek büyüklüklere (örneğin 1s, 10s, 100s, 1000s) yuvarlandığı paralel karma setlerini tutabilirsiniz. Bu sayede, gerekli aramaları daha hızlı yapmak için dişlileri yukarı ve aşağı hareket ettirebilirsiniz. Daha sonra performans bir O (log numberrange) olur, bence, yani sabittir, yani O (1).

Bunu daha net hale getirmek için 197 numarasının elinizde olduğunu hayal edin. 10'lu karma masaya çarptın, '190' ile, en yakın 10'a yuvarlandı. Herhangi bir şey? Hayır. Diyelim ki 120'ye kadar isabet edene kadar 10'larda iniyorsunuz. Sonra 1s hashtable'da 129'dan başlayabilir, daha sonra bir şeye çarpıncaya kadar 128, 127 deneyebilirsiniz. Bağlantılı listede 197 sayısını nereye koyacağınızı buldunuz. Bunu koyarken, 1s kodunu 197 girişiyle, 10'larının 190, 100'lerin 100'lü sayısıyla vb. Burada yapmanız gerekenler, sayı aralığının günlüğünün 10 katıdır.

Bazı ayrıntıları yanlış anlamış olabilirim, ancak bu programcıların değişimi olduğu ve bağlamın röportajları olduğu için yukarıdakilerin bu durum için yeterince ikna edici bir cevap olduğunu umuyorum.

EDIT Paralel karma çizelge şemasını açıklamak için buraya bazı ekstra detaylar ekledim ve bahsettiğim zayıf doğrusal aramaların nasıl bir O (1) araması ile değiştirilebileceği anlamına geliyor. Tabii ki bir sonraki en düşük sayıyı aramaya gerek olmadığını da gördüm, çünkü doğrudan en düşük sayıya sahip olan karışıma bakarak ve bir sonraki elemana ilerleyerek doğrudan adım atabilirsiniz.


1
Arama, ekle işlevinin bir parçası olmalıdır - bunlar bağımsız işlev değildir. Aramanız O (n) olduğundan, ekleme işleviniz de O (n) 'dir.
Kirk Broadhurst

Hayır. Numara alanını daha hızlı bir şekilde geçmek için daha fazla karmaşanın kullanıldığı, açıkladığım stratejiyi kullanarak, O (1). Lütfen cevabımı tekrar okuyunuz.
Benedict

1
@Benedict, cevabınız oldukça açık bir şekilde 4. ve 7. adımlarda doğrusal aramaları olduğunu söylüyor. Doğrusal aramalar O (1) değil.
Peter Taylor

Evet, öyle ama ben sonra halledeceğim. Gerisini okumanın bir sakıncası var mı lütfen? Gerekirse, cevabımı fazlasıyla netleştirmek için cevabımı düzenleyeceğim.
Benedict

@Benedict Haklısın - arama hariç, cevabınız O (1). Ne yazık ki bu çözüm arama olmadan işe yaramaz.
Kirk Broadhurst

1

Sayıların Tam Sayı gibi sabit bir veri türünde olduğunu varsayabilir miyiz? Öyleyse, eklenen her bir sayının bir taksitli tutun. Bu bir O (1) işlemidir.

  1. Olası sayılar kadar çok sayıda eleman içeren bir dizi bildirin:
  2. Her numarayı yayınlandığı sırada okuyun.
  3. Numarayı girin. Asla ihtiyaç duymayacağınız şekilde, bu sayı zaten 100 kez konuşulmuşsa yoksayın. Bu, taşmaların sınırsız sayıda konuşmasını engeller.
  4. 2. adımdan itibaren tekrarlayın.

VB.Net kodu:

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

Listeye geri döndüğünüzde istediğiniz kadar uzun sürebilir. Basitçe listenin sonundan itterat edin ve kaydedilen en yüksek 100 değerden oluşan yeni bir liste oluşturun. Bu bir O (n) işlemidir, ancak bu anlamsızdır.

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

Düzenleme: Aslında, sabit bir veri türü olup olmadığı gerçekten önemli değil. Bellek (veya sabit disk) tüketimi üzerine herhangi bir sınırlama getirilmediğinden, bu çalışmayı herhangi bir pozitif tamsayı aralığı için yapabilirsiniz.


1

100 sayı, bir dizide, 100 boyutunda kolayca saklanır. Eldeki görev göz önüne alındığında, herhangi bir ağaç, liste veya küme fazla kullanılır.

Gelen numara dizideki en düşükten (= last) daha yüksekse, tüm girişleri gözden geçirin. İlk numaranızı yeni numaranızdan daha küçük bulduğunuzda (bunu yapmak için süslü aramalar kullanabilirsiniz), dizinin geri kalan kısmında gezinerek her girişi "aşağı" birer birer aşağı doğru itin.

Listeyi baştan beri sıraladığınız için, herhangi bir sıralama algoritması çalıştırmanıza gerek kalmaz. Bu O (1).


0

Binary Max-Heap kullanabilirsiniz. Bir işaretçiyi minimum düğüme (bilinmeyen / boş olabilir) izlemeniz gerekir.

İlk 100 sayıyı öbeğe ekleyerek başlarsınız. Max en üstte olacak. Bu yapıldıktan sonra, daima 100 sayıyı orada tutacaksınız.

Sonra yeni bir numara aldığınızda:

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

Maalesef findMinimumNode, O (n) 'dir ve ekleme başına bir kez bu masrafa maruz kalırsınız (ancak ekleme sırasında değil :). Minimum düğümün çıkarılması ve yeni düğümün eklenmesi ortalama olarak O (1) 'dir, çünkü bunlar yığının dibine doğru eğilim gösterirler.

İkili bir Min-Yığınla başka bir yöne giderken, min en üsttedir, bu karşılaştırma için min'i bulmak için harikadır, ancak minimum değeri> min olan yeni bir sayı ile değiştirmeniz gerektiğinde berbattır. Bunun nedeni, min düğümünü (her zaman O (logN)) çıkarmanız ve ardından yeni düğümü (ortalama O (1)) takmanız gerektiğidir. Öyleyse, hala Max-Heap'tan daha iyi olan O (logN), ancak O (1) değil.

Tabii ki, eğer N sabittir, o zaman her zaman O (1). :)

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.