Kolay görüşme sorusu daha da zorlaştı: verilen sayılar 1..100, tam olarak verilen eksik sayıları bulun k eksik


1146

Bir süre önce ilginç bir iş görüşmesi deneyimi yaşadım. Soru gerçekten kolay başladı:

S1 : Biz sayıları içeren bir çanta var 1, 2, 3, ..., 100. Her sayı tam olarak bir kez görünür, bu nedenle 100 sayı vardır. Şimdi bir sayı rastgele çantadan çıkarıldı. Kayıp numarayı bulmak.

Bu röportaj sorusunu daha önce duymuştum, elbette, bu yüzden aşağıdakileri hızlı bir şekilde cevapladım:

A1 : Evet, sayıların toplamı 1 + 2 + 3 + … + Nolan (N+1)(N/2)(bkz : aritmetik dizi toplamını Wikipedia ). Çünkü N = 100, toplam 5050.

Böylece, torbada tüm sayılar varsa, toplam tam olarak olacaktır 5050. Bir sayı eksik olduğundan, toplam bundan daha az olacaktır ve fark bu sayıdır. Böylece o eksik sayıyı O(N)zaman ve O(1)mekanda bulabiliriz.

Bu noktada iyi yaptığımı sanıyordum, ama aniden soru beklenmedik bir dönüş yaptı:

S2 : Bu doğru, ama şimdi İKİ sayı eksikse bunu nasıl yapardınız ?

Daha önce bu varyasyonu hiç görmemiş / duymamış / düşünmemiştim, bu yüzden panikledim ve soruyu cevaplayamadım. Görüşmeci, düşünce sürecimi bilmede ısrar etti, bu yüzden belki de beklenen ürünle karşılaştırarak daha fazla bilgi alabileceğimizi ya da belki de ilk geçişten, vb. aslında çözüme açık bir yoldan ziyade karanlıkta.

Görüşmeci, ikinci bir denklemin gerçekten problemi çözmenin bir yolu olduğunu söyleyerek beni cesaretlendirmeye çalıştı. Bu noktada biraz üzgündüm (cevabı elden önce bilmediğim için) ve bunun genel (okuma: "yararlı") bir programlama tekniği olup olmadığını veya sadece bir hile / gotcha cevabı olup olmadığını sordum.

Görüşmecinin yanıtı beni şaşırttı: 3 eksik numara bulma tekniğini genelleştirebilirsiniz. Aslında, k eksik sayıları bulmak için genelleştirebilirsiniz .

Qk : Eğer çantada tam olarak k sayıları yoksa, onu nasıl verimli bulursunuz?

Bu birkaç ay önceydi ve bu tekniğin ne olduğunu hala anlayamadım. Açıkçası orada var Ω(N)bir kez en az tüm numaraları tarama gerektiğinden daha düşük bağlanmış zaman, ama görüşmeyi ısrar ZAMAN ve MEKAN çözme tekniği (eksi karmaşıklığı O(N)zaman giriş tarama) tanımlanır k değil N .

Yani buradaki soru basit:

  • Q2'yi nasıl çözersiniz ?
  • Q3'ü nasıl çözersiniz ?
  • Qk'u nasıl çözersiniz ?

Açıklamalar

  • Genellikle 1 .. N'den N numaraları vardır, sadece 1..100 değil.
  • Bu nedenle ek alan bitleri kullanarak, belirli bir bit değerine göre her sayı varlığı / yokluğu kodlama , örneğin bir bit kümesi kullanarak bariz küme tabanlı çözüm aramıyorum O(N). N ile orantılı olarak ek alan göze alamayız .
  • Aynı zamanda bariz ilk önce yaklaşımı da aramıyorum. Bu ve küme temelli yaklaşım bir röportajda belirtilmeye değer (uygulanması kolaydır ve N'ye bağlı olarak çok pratik olabilir). Holy Grail çözümünü arıyorum (uygulanması pratik olabilir veya olmayabilir, ancak yine de istenen asimtotik özelliklere sahiptir).

Bu yüzden, yine, girdiyi taramanız gerekir O(N), ancak yalnızca küçük miktarda bilgi yakalayabilirsiniz ( N değil k olarak tanımlanır ) ve daha sonra k eksik sayıları bir şekilde bulmalısınız .


7
@polygenelubricants Açıklamalar için teşekkür ederiz. "K (eksik sayıların sayısı olduğu O (N) zaman ve O (K) boşluğu kullanan bir algoritma arıyorum" başından beri net olurdu ;-)
Dave O.

7
Q1'in ifadesinde sayılara sırayla erişemeyeceğinizi kesin olarak belirtmelisiniz. Bu muhtemelen sizin için açık gibi görünüyor, ama soruyu hiç duymadım ve "çanta" terimi ("multiset" anlamına da geliyor) biraz kafa karıştırıcıydı.
Jérémie

7
Burada verilen cevaplar gülünç olduğu için lütfen aşağıdakileri okuyun: stackoverflow.com/questions/4406110/…

18
Sayıları toplamanın çözümü, sınırsız bir tamsayı için alan gereksiniminin O (1) olduğunu düşünmediğiniz sürece log (N) alanı gerektirir. Ancak sınırsız tamsayılara izin verirseniz, tek bir tamsayı ile istediğiniz kadar alanınız olur.
Udo Klein

3
Bu arada Q1 için çok güzel bir alternatif çözüm bilgisayar olabilir XORtüm sayıların 1için ndaha sonra verilen dizideki tüm sayılarla sonuca xoring. Sonunda eksik numaranız var. Bu çözümde, özetlemede olduğu gibi taşmayı önemsemenize gerek yoktur.
sbeliakov

Yanıtlar:


590

İşte Dimitris Andreou'nun bağlantısının bir özeti .

İ = 1,2, .., k olan i-inci güçlerin toplamını hatırlayın. Bu denklem sistemini çözme problemini azaltır

a 1 + a 2 + ... + a k = b 1

a 1 2 + a 2 2 + ... + a k 2 = b 2

...

a 1 k + a 2 k + ... + a k k = b k

Newton'un kimliklerini kullanarak b i bilmek hesaplamaya izin verir

c 1 = a 1 + a 2 + ... a k

c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k

...

c k = a 1 a 2 ... a k

Polinom (xa 1 ) ... (xa k ) ' yı genişletirseniz katsayılar tam olarak c 1 , ..., c k olacaktır - Viète'nin formüllerine bakın . Her polinom faktörü benzersiz olarak (polinom halkası bir Öklid alanıdır ), bu da i'nin permütasyona kadar benzersiz bir şekilde belirlendiği anlamına gelir .

Bu sayıları kurtarmak için güçleri hatırlamanın yeterli olduğunun bir kanıtıdır. Sabit k için bu iyi bir yaklaşımdır.

K değişen zaman Ancak, hesaplama doğrudan yaklaşım c 1 , ..., c k örneğin c beri prohibitely pahalı k tüm eksik sayılar, büyüklük n! / (Nk) ürünüdür !. Bunun üstesinden gelmek için, Z q alanında hesaplamalar yapın , burada q, n <= q <2n olacak şekilde asaldır - Bertrand'ın postülasında mevcuttur . Formül hala geçerli olduğundan ve polinomların çarpanlarına ayrılması hala benzersiz olduğundan kanıtın değiştirilmesi gerekmez. Ayrıca, Berlekamp veya Cantor- Zassenhaus gibi sonlu alanlar üzerinde çarpanlara ayırma için bir algoritmaya ihtiyacınız vardır .

K sabiti için yüksek seviye sözde kodu:

  • Verilen sayıların i'inci güçlerini hesapla
  • Bilinmeyen sayıların i-inci güçlerinin toplamını almak için çıkartın. Toplamları çağırın b i .
  • B dan hesaplama katsayıları Kullanım Newton'un kimlikler i ; onları ara c i . Temel olarak, C 1 = b 1 ; c 2 = (C 1 b 1 - b 2 ) / 2; kesin formüller için Wikipedia'ya bakın
  • Faktör polinomu x k -c 1 x k-1 + ... + c k .
  • Polinomun kökleri gereken sayı 1 , ..., a k'dır .

Değişken k için, örneğin Miller-Rabin'i kullanarak bir n <= q <2n bulun ve tüm sayıları azaltılmış modulo q ile adımları gerçekleştirin.

DÜZENLEME: Bu yanıtın önceki sürümü, q'nun asal olduğu Z q yerine , karakteristik 2 (q = 2 ^ (log n)) sonlu bir alanın kullanılabileceğini belirtmiştir . Newton'un formülleri k'ye kadar rakamlara bölünmeyi gerektirdiğinden, durum böyle değildir.


6
Asal bir alan kullanmak zorunda değilsiniz, ayrıca kullanabilirsiniz q = 2^(log n). (
Süper

49
+1 Bu gerçekten, gerçekten akıllı. Aynı zamanda, gerçekten çabaya değip değmeyeceği veya bu çözümün oldukça yapay bir soruna (parçalarının) başka bir şekilde tekrar kullanıp kullanılamayacağı tartışmalıdır. Ve bu gerçek bir dünya sorunu olsa bile, birçok platformda en önemsiz O(N^2)çözüm muhtemelen bu güzelliğin makul derecede yüksek bir performanstan daha iyi performans gösterecektir N. Bana şunu düşündürüyor: tinyurl.com/c8fwgw Yine de harika bir iş çıkardın ! Ben tüm matematik tarama sabır olmazdı :)
back2dos

167
Bence bu harika bir cevap. Bence bu, bir röportaj sorusunun eksik sayıları bir rakamın ötesine uzatmanın ne kadar kötü olacağını da gösteriyor. Birincisi bile bir tür gotchya, ama temelde "biraz röportaj hazırlığı yaptığını" gösterecek kadar yaygın. Ama bir CS büyük mahallenin bilmesini beklemek k = 1 (özellikle bir röportajda "yerinde") biraz saçma.
corsiKa

5
Bu, girdide Reed Solomon kodlamasını etkili bir şekilde yapıyor.
David Ehrmann

78
Ben tüm bir sayı girerek bahis hash setve 1...Nsayılar eksik olup olmadığını belirlemek için aramalar kullanarak paketi üzerinden yineleme , kvaryasyonlar, ortalama en hızlı , en hata ayıklanabilir en sürdürülebilir ve anlaşılabilir çözüm olacağını belirlemek için . Tabii ki matematik yolu etkileyici ama yol boyunca bir yerde bir matematikçi değil, bir mühendis olmanız gerekiyor. Özellikle iş söz konusu olduğunda.
v.oddou

243

Muthukrishnan - Veri Akışı Algoritmaları: Bulmaca 1: Eksik Sayıları Bulma birkaç sayfasını okuyarak bulacaksınız . Tam olarak aradığınız genellemeyi gösterir . Muhtemelen bu, görüşmecinizin okuduğu şey ve neden bu soruları yöneltti.

Şimdi, sadece insanlar Muthukrishnan'ın tedavisinin aldığı veya yerini alan cevapları silmeye başlar ve bu metni bulmayı kolaylaştırırsa. :)


Ayrıca sdcvvc'in sözde kodunu da içeren doğrudan ilgili cevabına bakın ( hurray ! Bu zor matematik formülasyonlarını okumaya gerek yok :)) (teşekkürler, harika iş!).


Oooh ... Bu ilginç. İtiraf etmeliyim ki matematik tarafından biraz kafam karıştı ama jsut onu gözden kaçırdım. Daha sonra bakmak için açık bırakabilir. :) Ve bu bağlantıyı daha kolay elde etmek için +1. ;-)
Chris

2
Google kitaplar bağlantısı benim için çalışmıyor. İşte daha iyi bir sürüm [PostScript Dosyası].
Heinrich Apfelmus

9
Vay. Ben bunun upvoted olmasını beklemiyorduk! Çözüme son kez başvurdum (Knuth's, bu durumda), kendim çözmeye çalışmak yerine, aslında indirildi: stackoverflow.com/questions/3060104/… İçimdeki kütüphaneci sevinir, teşekkürler :)
Dimitris Andreou

@Afelfelmus, bunun bir taslak olduğunu unutmayın. (Tabii ki sizi suçlamıyorum, kitabı bulmadan önce neredeyse bir yıl boyunca gerçek şeyler için taslağı karıştırdım). Btw bağlantı çalışmadıysa, books.google.com adresine gidebilir ve "Muthukrishnan veri akışı algoritmalarını" (tırnak işaretleri olmadan) arayabilirsiniz, ilk açılan penceredir.
Dimitris Andreou

2
Burada verilen cevaplar gülünç olduğu için lütfen aşağıdakileri okuyun: stackoverflow.com/questions/4406110/…

174

Q2'yi hem sayıların hem de sayıların karelerini toplayarak çözebiliriz .

Daha sonra sorunu şu şekilde azaltabiliriz:

k1 + k2 = x
k1^2 + k2^2 = y

Toplamlar beklenen değerlerin nerede xve yne kadar altındadır.

İkame bize şunu verir:

(x-k2)^2 + k2^2 = y

Hangi eksik numaralarımızı belirlemek için çözebiliriz.


7
+ 1; Maple'daki formülleri belirli sayılar için denedim ve işe yarıyor. Yine de kendimi neden işe yaradığına ikna edemedim.
polygenelubricants

4
@polygenelubricants: Eğer doğruluğunu kanıtlamak istiyorsa, öncelikle her zaman sağladığını gösterir bir olduğunu, her zaman kümesinden bunları kaldırırken, sahip setin sonuna kadar sonuçlanacaktır, sayıların bir çift üretir (doğru çözüm gözlenen toplam ve karelerin toplamı). Oradan, tekliği kanıtlamak, sadece bir çift sayı ürettiğini göstermek kadar basittir.
Anon.

5
Denklemlerin doğası, bu denklemden iki k2 değeri alacağınız anlamına gelir. Bununla birlikte, k1 üretmek için kullandığınız ilk denklemden, bu iki k2 değerinin k1'in diğer değer olduğu anlamına geldiğini görebilirsiniz, böylece tam tersi olan aynı sayılara sahip iki çözümünüz vardır. Eğer k1> k2'yi abitrar olarak beyan ettiyseniz, kuadratik denklem için sadece bir çözümünüz ve dolayısıyla genel olarak bir çözümünüz olurdu. Ve açıkça sorunun doğası gereği bir cevap her zaman vardır, bu yüzden her zaman işe yarar.
Chris

3
Belirli bir k1 + k2 toplamı için birçok çift vardır. Bu çiftleri K1 = a + b ve K2 = ab olarak yazabiliriz, burada a = (K1 + k2 / 2). a, belirli bir miktar için benzersizdir. Karelerin toplamı (a + b) ** 2 + (ab) ** 2 = 2 * (a 2 + b 2). Belirli bir K1 + K2 toplamı için a 2 terimi sabittir ve b2 terimi nedeniyle kareler toplamının benzersiz olacağını görürüz . Bu nedenle, x ve y değerleri bir çift tamsayı için benzersizdir.
phkahler

8
Bu harika. @ user3281743 İşte bir örnek. Eksik sayılar (k1 ve k2) 4 ve 6 olsun. Toplam (1 -> 10) = 55 ve Toplam (1 ^ 2 -> 10 ^ 2) = 385. Şimdi x = 55 - (Toplam (Kalan tüm sayılar) )) ve y = 385 - (Toplam (kalan tüm sayıların kareleri)) böylece x = 10 ve y = 52. Bizi aşağıdaki gibi bırakan şekilde gösterildiği gibi değiştirin: (10 - k2) ^ 2 + k2 ^ 2 = 52 basitleştirin: 2k ^ 2 - 20k + 48 = 0. İkinci dereceden denklemi çözmek size cevap olarak 4 ve 6 verir.
AlexKoren

137

@J_random_hacker'ın belirttiği gibi, bu O (n) zamanında ve O (1) uzayda yinelenenleri bulmaya oldukça benzer ve burada cevabımın bir uyarlaması da burada çalışıyor.

"Torbanın" 1 tabanlı bir A[]boyut dizisi ile temsil edildiği varsayılarak, N - kQk'yi O(N)zamanında çözebiliriz veO(k) ek alan olarak .

İlk olarak, dizimizi elemanlara A[]göre genişletiyoruz k, böylece şimdi boyutta N. Bu O(k)ek alan. Daha sonra aşağıdaki sözde kod algoritmasını çalıştırıyoruz:

for i := n - k + 1 to n
    A[i] := A[1]
end for

for i := 1 to n - k
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 1 to n
    if A[i] != i then 
        print i
    end if
end for

İlk döngü k, dizideki ilk girişle aynı ek girişleri başlatır (bu, dizide zaten mevcut olduğunu bildiğimiz uygun bir değerdir - bu adımdan sonra, ilk boyut dizisinde eksik olan girişlerN-k vardır genişletilmiş dizide hala eksik).

İkinci döngü genişletilmiş diziye izin verir, böylece eleman xen az bir kez mevcutsa, bu girişlerden biri pozisyonda olurA[x] .

Yuvalanmış bir döngüye sahip olmasına rağmen, yine de O(N)zaman içinde çalışır - bir takas yalnızca iböyle bir şey A[i] != iolduğunda gerçekleşir ve her takas, en azından bir öğeyi A[i] == idaha önce doğru olmayan bir şekilde ayarlar . Bu, toplam takas sayısının (ve dolayısıyla whiledöngü gövdesinin toplam yürütme sayısının ) en fazla olduğu anlamına gelir N-1.

Üçüncü döngü, dizinin ideğer tarafından kullanılmayan dizinlerini yazdırır i- bu, ieksik olması gerektiği anlamına gelir .


4
Neden bu kadar az insanın bu cevabı oyladığını ve hatta doğru cevap olarak işaretlemediğini merak ediyorum. İşte Python'daki kod. O (n) zamanında çalışır ve fazladan O (k) alanına ihtiyaç duyar. pastebin.com/9jZqnTzV
wall-e

3
@caf bu bitleri ayarlamak ve bit 0 olduğu yerleri saymaya oldukça benzer. Ve ben bir tamsayı dizi oluştururken daha fazla bellek işgal düşünüyorum.
Fox

5
"Bitlerin ayarlanması ve bitin 0 olduğu yerlerin sayılması" O (n) ekstra alan gerektirir, bu çözüm O (k) ekstra alanın nasıl kullanılacağını gösterir.
caf

7
Akış olarak giriş olarak çalışmaz ve giriş dizisini değiştirir (çok sevdim ve fikir verimli olsa da).
comco

3
@ v.oddou: Hayır, sorun değil. Takas değişecektir A[i], yani bir sonraki yineleme bir öncekiyle aynı iki değeri karşılaştırmayacaktır. Yeni A[i], son döngü ile aynı olacaktır A[A[i]], ancak yeni A[A[i]], yeni bir değer olacaktır. Deneyin ve görün.
caf

128

4 yaşında bir çocuktan bu sorunu çözmesini istedim. Sayıları sıraladı ve sonra saydı. Bu, O (mutfak zemini) için bir alan gereksinimine sahiptir ve birçok topun eksik olduğu kadar kolaydır.


20
;) 4 yaşındaki çocuğunuz 5'e yaklaşıyor olmalı ve / veya bir dahi. 4 yaşındaki kızım bile henüz 4 doğru saymak olamaz. Dürüst olmak gerekirse, diyelim ki "4" nin varlığını zar zor bütünleştirdi. aksi halde şimdiye kadar atlardı. "1,2,3,5,6,7" olağan sayma dizisiydi. Birlikte kalem eklemesini istedim ve sıfırdan tekrar sayılarak 1 + 2 = 3'ü yönetecekti. Endişeliyim aslında ...: '(meh ..
v.oddou

basit ama etkili bir yaklaşım.
PabTorre

6
O (mutfak zemini) haha ​​- ama O (n ^ 2) olmaz mı?

13
O (m²) sanırım :)
Viktor Mellgren

1
@phuclv: Bu sorunun cevabı "Bunun O (mutfak zemini) için bir alan ihtiyacı var" dedi. Ama her durumda, bu sıralama bir örneğidir olabilir elde edilebilir O (n) bakınız --- zaman bu tartışma .
Anthony Labarre

36

Emin değilim, en verimli çözüm, ancak tüm girişler üzerinde döngü ve hangi sayıların ayarlandığını hatırlamak için bir bit kümesi kullanın ve sonra 0 bit test edin.

Basit çözümleri seviyorum - ve hatta toplamı veya karelerin toplamını hesaplamaktan daha hızlı olabileceğine inanıyorum.


11
Bu açık cevabı önerdim, ancak görüşmecinin istediği bu değil. Soruda bunun aradığım cevap olmadığını açıkça söyledim. Bir diğer açık cevap: önce sıralayın. Her ikisi de çok basit çözümler olmasına rağmen, ne O(N)sayım sıralaması ne de O(N log N)karşılaştırma sıralaması aradığım şey değil.
polygenelubricants

@polygenelubricants: Sorunuzda söylediğiniz yeri bulamıyorum. Eğer bit kümesini sonuç olarak görürseniz, ikinci bir geçiş olmaz. Karmaşıklık (eğer N'nin sabit olduğunu düşünürsek, görüşmecinin söylediği gibi, karmaşıklığın " k değil N olarak tanımlanır ") O (1) ve daha "temiz" bir sonuç oluşturmanız gerekiyorsa, Olabileceğiniz en iyisi olan O (k) elde edin, çünkü temiz sonucu oluşturmak için her zaman O (k) gerekir.
Chris Lercher

"Belirgin küme tabanlı çözüm (örneğin, bir bit kümesi kullanarak)
aramıyorum unutmayın

9
@hmt: Evet, soru birkaç dakika önce düzenlendi. Sadece bir röportaj yapılan kişiden bekleyebileceğim yanıtı veriyorum ... Yapay olarak optimal olmayan bir çözüm inşa etmek (ne yaparsanız yapın O (n) + O (k) zamanını yenemezsiniz) ' bana mantıklı değil - O (n) ek alan göze alamaz, ancak soru bu konuda açık değildir.
Chris Lercher

3
Daha fazla açıklığa kavuşturmak için soruyu tekrar düzenledim. Geri bildirimi / cevabı takdir ediyorum.
polygenelubricants

33

Ben matematik kontrol etmedim, ama Σ(n^2)biz hesaplamak gibi aynı geçişte hesaplama Σ(n)iki eksik sayı almak için yeterli bilgi sağlayacak şüpheli Σ(n^3), üç varsa da yapın, vb.


15

Sayıların toplamına dayalı çözümlerle ilgili sorun, büyük üslü sayılarla depolama ve çalışma maliyetini dikkate almamalarıdır ... pratikte, çok büyük n için çalışması için büyük bir sayı kütüphanesi kullanılacaktır . Bu algoritmalar için alan kullanımını analiz edebiliriz.

Sdcvvc ve Dimitris Andreou algoritmalarının zaman ve mekan karmaşıklığını analiz edebiliriz.

Depolama:

l_j = ceil (log_2 (sum_{i=1}^n i^j))
l_j > log_2 n^j  (assuming n >= 0, k >= 0)
l_j > j log_2 n \in \Omega(j log n)

l_j < log_2 ((sum_{i=1}^n i)^j) + 1
l_j < j log_2 (n) + j log_2 (n + 1) - j log_2 (2) + 1
l_j < j log_2 n + j + c \in O(j log n)`

Yani l_j \in \Theta(j log n)

Kullanılan toplam depolama alanı: \sum_{j=1}^k l_j \in \Theta(k^2 log n)

Kullanılan alan: hesaplama varsayarak a^jalır ceil(log_2 j)zaman, toplam süre:

t = k ceil(\sum_i=1^n log_2 (i)) = k ceil(log_2 (\prod_i=1^n (i)))
t > k log_2 (n^n + O(n^(n-1)))
t > k log_2 (n^n) = kn log_2 (n)  \in \Omega(kn log n)
t < k log_2 (\prod_i=1^n i^i) + 1
t < kn log_2 (n) + 1 \in O(kn log n)

Toplam kullanılan süre: \Theta(kn log n)

Bu zaman ve alan tatmin ediciyse, basit bir özyinelemeli algoritma kullanabilirsiniz. Bırakın b! İ torbadaki i girişi, n çıkarmadan önce sayı sayısı ve k çıkarılma sayısı. Haskell sözdiziminde ...

let
  -- O(1)
  isInRange low high v = (v >= low) && (v <= high)
  -- O(n - k)
  countInRange low high = sum $ map (fromEnum . isInRange low high . (!)b) [1..(n-k)]
  findMissing l low high krange
    -- O(1) if there is nothing to find.
    | krange=0 = l
    -- O(1) if there is only one possibility.
    | low=high = low:l
    -- Otherwise total of O(knlog(n)) time
    | otherwise =
       let
         mid = (low + high) `div` 2
         klow = countInRange low mid
         khigh = krange - klow
       in
         findMissing (findMissing low mid klow) (mid + 1) high khigh
in
  findMising 1 (n - k) k

Kullanılan depolama alanı: O(k)liste O(log(n))için, yığın için: O(k + log(n)) Bu algoritma daha sezgiseldir, aynı zaman karmaşıklığına sahiptir ve daha az alan kullanır.


1
+1, hoş görünüyor ama snippet # 1'de satır 4'ten satır 5'e gitmemi kaybettiniz - bunu daha fazla açıklayabilir misiniz? Teşekkürler!
j_random_hacker

isInRangebir O (log n) değil, O (1) : o karşılaştırma, öylelikle, aralık 1..n sayıları karşılaştırır O (log n) bit. Bu hatanın analizin geri kalanını ne ölçüde etkilediğini bilmiyorum.
jcsahnwaldt diyor GoFundMonica

14

Bir dakika bekle. Soruda belirtildiği gibi, çantada 100 numara var. K ne kadar büyük olursa olsun, sorun sabit bir zamanda çözülebilir, çünkü bir kümeyi kullanabilir ve bir döngünün en fazla 100 - k yinelemesinde sayıları kümeden kaldırabilirsiniz. 100 sabittir. Kalan sayı kümesi cevabınızdır.

Çözümü 1'den N'ye kadar olan sayılara genelleştirirsek, N dışında hiçbir şey değişmez, bu nedenle O (N - k) = O (N) süresinde oluruz. Örneğin, bir bit kümesi kullanırsak, bitleri O (N) zamanda 1 olarak ayarlar, sayılar arasında yineleme yapar, gittikçe bitleri 0 olarak ayarlarız (O (Nk) = O (N)) ve sonra cevabı var.

Bana öyle geliyor ki görüşmeci size son setin içeriğini O (N) saatinden ziyade O (k) saatinde nasıl bastıracağınızı soruyordu . Açıkça, bir bit seti ile, sayıyı yazdırıp yazdırmamanız gerektiğini belirlemek için tüm N bitlerini tekrarlamanız gerekir. Ancak, setin uygulanma şeklini değiştirirseniz, sayıları k yinelemelerde yazdırabilirsiniz. Bu, sayıları hem karma kümesinde hem de çift bağlantılı bir listede saklanacak bir nesneye koyarak yapılır. Bir nesneyi karma kümesinden kaldırdığınızda, nesneyi listeden de kaldırırsınız. Cevaplar şimdi k uzunluğunda olan listede bırakılacaktır.


9
Bu cevap çok basit ve hepimiz basit cevapların işe yaramadığını biliyoruz! ;) Cidden, orijinal soru muhtemelen O (k) alan gereksinimini vurgulamalıdır.
DK.

Sorun basit değil, ancak harita için O (n) ek bellek kullanmanız gerekecek. Sorun beni sürekli zaman ve sabit bellekte çözdü
Mojo Risin

3
Eminim minimal çözümün en az O (N) olduğunu kanıtlayabilirsiniz. çünkü daha azı, bazı sayılara bile bakmadığınız anlamına gelir ve hiçbir sipariş belirtilmediğinden, TÜM sayılara bakmak zorunludur.
v.oddou

Girdiye akış olarak bakarsak ve n bellekte tutulamayacak kadar büyükse, O (k) bellek gereksinimi mantıklı olur. Yine de hashing'i kullanabiliriz: Sadece k ^ 2 kovaları yapın ve her birinde basit toplam algoritmasını kullanın. Bu sadece k ^ 2 bellek ve yüksek başarı olasılığı elde etmek için birkaç kova daha kullanılabilir.
Thomas Ahle

8

2 (ve 3) eksik sayı sorusunu çözmek için, bölümleme yerinde yapılırsa quickselectortalama olarak çalışan O(n)ve sabit belleği kullanan değişiklik yapabilirsiniz .

  1. Kümeyi rastgele bir pivot ile ilgili olarak , pivottan daha küçük sayılar içeren ve pivottan daha büyük sayılar içeren pbölümlere ayırın.lr

  2. Pivot değerini her bölümün boyutuyla ( p - 1 - count(l) = count of missing numbers in lve n - count(r) - p = count of missing numbers in r) karşılaştırarak eksik olan 2 sayının hangi bölümlerde olduğunu belirleyin

  3. a) Her bölümde bir sayı eksikse, eksik sayıları bulmak için toplamlar farkını kullanın.

    (1 + 2 + ... + (p-1)) - sum(l) = missing #1 ve ((p+1) + (p+2) ... + n) - sum(r) = missing #2

    b) Bir bölüm her iki sayıyı da eksikse ve bölüm boşsa, eksik sayılar ya (p-1,p-2)da (p+1,p+2) hangi bölümün sayıları eksik olduğuna bağlı olarak değişir.

    Bir bölümde 2 sayı eksik ama boş değilse, o parttona geri dönün.

Yalnızca 2 eksik sayı olduğunda, bu algoritma her zaman en az bir bölüm atar, bu nedenle O(n) hızlı seçimin ortalama zaman karmaşıklığını . Benzer şekilde, 3 eksik sayı ile bu algoritma her geçişte en az bir bölüm atar (çünkü 2 eksik sayıda olduğu gibi, en fazla 1 bölüm birden fazla eksik sayı içerecektir). Ancak, daha fazla eksik sayı eklendiğinde performansın ne kadar düştüğünden emin değilim.

İşte etmeyen bir uygulama var değil bu örnek uzay gereksinimi uymayan yüzden, yerinde bölümleme ama algoritmanın adımları göstermek yapar:

<?php

  $list = range(1,100);
  unset($list[3]);
  unset($list[31]);

  findMissing($list,1,100);

  function findMissing($list, $min, $max) {
    if(empty($list)) {
      print_r(range($min, $max));
      return;
    }

    $l = $r = [];
    $pivot = array_pop($list);

    foreach($list as $number) {
      if($number < $pivot) {
        $l[] = $number;
      }
      else {
        $r[] = $number;
      }
    }

    if(count($l) == $pivot - $min - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($min, $pivot-1)) - array_sum($l) . "\n";
    }
    else if(count($l) < $pivot - $min) {
      // more than 1 missing number, recurse
      findMissing($l, $min, $pivot-1);
    }

    if(count($r) == $max - $pivot - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($pivot + 1, $max)) - array_sum($r) . "\n";
    } else if(count($r) < $max - $pivot) {
      // mroe than 1 missing number recurse
      findMissing($r, $pivot+1, $max);
    }
  }

gösteri


Kümeyi bölümlere ayırmak doğrusal boşluk kullanmak gibidir. En azından bir akış ayarında çalışmaz.
Thomas Ahle

Bkz @ThomasAhle en.wikipedia.org/wiki/Selection_algorithm#Space_complexity . seti yerinde ayırmak için sadece O (1) ek alan gerekir - doğrusal alan değil. Bir akış ayarında O (k) ek alan olurdu, ancak orijinal soru akıştan bahsetmez.
FuzzyTree

Doğrudan değil, ama yazıyor "girdiyi O (N) olarak taramalısınız, ancak genellikle akışın tanımı olan sadece küçük miktarda (k cinsinden tanımlanmış olarak tanımlanır) yakalayabilirsiniz"). N boyutunda bir dizi olmadıkça, bölümleme için tüm sayıları taşımak gerçekten mümkün değildir. Bu sadece cadının bu kısıtlamayı göz ardı ettiği birçok cevaba sahip olması.
Thomas Ahle

1
Ama dediğin gibi, daha fazla sayı eklendikçe performans düşebilir mi? Ayrıca, her zaman mükemmel bir kesim elde etmek için doğrusal zaman medyan algoritmasını kullanabiliriz, ancak k sayıları 1, ..., n'de iyi yayılmışsa, budamadan önce "derin" logk seviyeleri hakkında gitmek zorunda kalmazsınız. herhangi bir şube var mı?
Thomas Ahle

2
En kötü çalışma süresi gerçekten nlogk çünkü tüm girdiyi en çok logk zamanında işlemeniz gerekir ve daha sonra geometrik bir sekanstır (en fazla n öğeyle başlayan). Alan gereksinimleri, düz özyineleme ile uygulandığında logn'dur, ancak gerçek bir hızlı seçim çalıştırılarak ve her bölümün doğru uzunluğunu sağlayarak O (1) yapılabilir.
emu

7

İşte akıllıca hileler olmadan ve basit bir şekilde k fazla ekstra depolama alanı kullanan bir çözüm. Yürütme süresi O (n), fazladan boşluk O (k). Bunun ilk önce çözümü okumadan veya dahi olmadan çözülebileceğini kanıtlamak için:

void puzzle (int* data, int n, bool* extra, int k)
{
    // data contains n distinct numbers from 1 to n + k, extra provides
    // space for k extra bits. 

    // Rearrange the array so there are (even) even numbers at the start
    // and (odd) odd numbers at the end.
    int even = 0, odd = 0;
    while (even + odd < n)
    {
        if (data [even] % 2 == 0) ++even;
        else if (data [n - 1 - odd] % 2 == 1) ++odd;
        else { int tmp = data [even]; data [even] = data [n - 1 - odd]; 
               data [n - 1 - odd] = tmp; ++even; ++odd; }
    }

    // Erase the lowest bits of all numbers and set the extra bits to 0.
    for (int i = even; i < n; ++i) data [i] -= 1;
    for (int i = 0; i < k; ++i) extra [i] = false;

    // Set a bit for every number that is present
    for (int i = 0; i < n; ++i)
    {
        int tmp = data [i];
        tmp -= (tmp % 2);
        if (i >= even) ++tmp;
        if (tmp <= n) data [tmp - 1] += 1; else extra [tmp - n - 1] = true;
    }

    // Print out the missing ones
    for (int i = 1; i <= n; ++i)
        if (data [i - 1] % 2 == 0) printf ("Number %d is missing\n", i);
    for (int i = n + 1; i <= n + k; ++i)
        if (! extra [i - n - 1]) printf ("Number %d is missing\n", i);

    // Restore the lowest bits again.
    for (int i = 0; i < n; ++i) {
        if (i < even) { if (data [i] % 2 != 0) data [i] -= 1; }
        else { if (data [i] % 2 == 0) data [i] += 1; }
    }
}

İstedin (data [n - 1 - odd] % 2 == 1) ++odd;mi
Charles

2
Bunun nasıl çalıştığını açıklayabilir misiniz? Anlamıyorum.
Teepeemm

Geçici depolama için bir dizi (n + k) booleans kullanabilseydim, çözüm çok, çok, basit olurdu, ancak buna izin verilmiyor. Bu yüzden verileri yeniden düzenlerim, çift sayıları başında, tek sayıları dizinin sonuna koyarım. Şimdi bu n sayılarının en düşük bitleri geçici depolama için kullanılabilir, çünkü kaç tane çift ve tek sayı olduğunu biliyorum ve en düşük bitleri yeniden oluşturabilirim! Bu n bit ve k ekstra bit tam olarak ihtiyacım olan (n + k) boolean.
gnasher729

2
Veriler bellekte tutulamayacak kadar büyük olsaydı ve yalnızca akış olarak gördüyseniz bu işe yaramaz. Lezzetli hacky gerçi :)
Thomas Ahle

Uzay karmaşıklığı O (1) olabilir. İlk geçişte, <(n - k) tüm sayıları 'ekstra' kullanmadan tam olarak bu algoritma ile işlersiniz. İkinci geçişte parite bitlerini tekrar temizler ve sayıları indekslemek için ilk k konumlarını kullanırsınız (nk) .. (n).
emu

5

Her sayının var olup olmadığını kontrol edebilir misiniz? Evetse, bunu deneyebilirsiniz:

S = torbadaki tüm sayıların toplamı (S <5050)
Z = eksik sayıların toplamı 5050 - S

Eksik sayılar ise xve yo zaman:

x = Z - y ve
maks. (x) = Z - 1

Eğer gelen aralığını kontrol Yani 1etmek max(x)ve numarasını bulmak


1
Ne max(x)demek, ne zaman xbir sayı?
Thomas Ahle

2
muhtemelen sayı kümesinden maksimum anlamına gelir
JavaHopper

2'den fazla
numaramız

4

Bu algoritma 1. soru için işe yarayabilir:

  1. İlk 100 tamsayıdan xveya ön hesaplama (val = 1 ^ 2 ^ 3 ^ 4 .... 100)
  2. xveya giriş akımından gelmeye devam ettikçe elemanları (val1 = val1 ^ next_input)
  3. son cevap = val ^ val1

Ya da daha iyisi:

def GetValue(A)
  val=0
  for i=1 to 100
    do
      val=val^i
    done
  for value in A:
    do
      val=val^value 
    done
  return val

Bu algoritma aslında iki eksik sayı için genişletilebilir. İlk adım aynı kalır. GetValue'yu iki eksik numarayla çağırdığımızda sonuç bir a1^a2eksik iki sayıdır. Diyelimki

val = a1^a2

Şimdi val'dan a1 ve a2'yi elemek için val'da herhangi bir set biti alıyoruz. Diyelim ki ithbit val olarak ayarlanmış. Bu, a1 ve a2'nin ithbit konumunda farklı pariteye sahip olduğu anlamına gelir . Şimdi orijinal dizide başka bir yineleme yapıyoruz ve iki xor değerini koruyoruz. Biri, bit biti ayarlanmış sayılar ve diğeri de bit biti ayarlanmamış sayılar içindir. Şimdi iki kova rakamımız var a1 and a2ve farklı kovalarda kalacak şekilde garantili . Şimdi her bir kovada eksik bir eleman bulmak için yaptığımızla aynı şeyi tekrarlayın.


Bu sadece sorunu çözüyor k=1, değil mi? Ama xortoplamları kullanmaktan hoşlanıyorum , biraz daha hızlı görünüyor.
Thomas Ahle

@ThomasAhle Evet. Cevabımda bunu aradım.
bashrc

Sağ. K = 2 için "ikinci derece" xor'un ne olabileceği hakkında bir fikriniz var mı? Toplam için kareler kullanmaya benzer şekilde, xor için "kare" yapabilir miyiz?
Thomas Ahle

1
@ThomasAhle 2 eksik numara için çalışacak şekilde değiştirildi.
bashrc

Bu benim en sevdiğim yol :)
robert king

3

Her iki listenin toplamına ve her iki listenin ürününe sahipseniz, Q2'yi çözebilirsiniz.

(l1 orijinal, l2 değiştirilmiş listedir)

d = sum(l1) - sum(l2)
m = mul(l1) / mul(l2)

Bir aritmetik serinin toplamı ilk ve son terimlerin ortalamasının n katı olduğu için bunu optimize edebiliriz:

n = len(l1)
d = (n/2)*(n+1) - sum(l2)

Şimdi biliyoruz (a ve b kaldırılan sayılarsa):

a + b = d
a * b = m

Böylece şunları yeniden düzenleyebiliriz:

a = s - b
b * (s - b) = m

Ve çarpın:

-b^2 + s*b = m

Ve sağ taraf sıfır olacak şekilde yeniden düzenleyin:

-b^2 + s*b - m = 0

Sonra ikinci dereceden formülle çözebiliriz:

b = (-s + sqrt(s^2 - (4*-1*-m)))/-2
a = s - b

Örnek Python 3 kodu:

from functools import reduce
import operator
import math
x = list(range(1,21))
sx = (len(x)/2)*(len(x)+1)
x.remove(15)
x.remove(5)
mul = lambda l: reduce(operator.mul,l)
s = sx - sum(x)
m = mul(range(1,21)) / mul(x)
b = (-s + math.sqrt(s**2 - (-4*(-m))))/-2
a = s - b
print(a,b) #15,5

Ben sqrt karmaşıklığını bilmiyorum, azaltmak ve toplam fonksiyonlar bu yüzden bu çözümün karmaşıklığı çalışamam (kimse biliyorsa lütfen aşağıda yorum.)


Hesaplamak için ne kadar zaman ve bellek kullanıyor x1*x2*x3*...?
Thomas Ahle

@ThomasAhle Listenin uzunluğundaki O (n) -zamanı ve O (1) -zamanıdır, ancak gerçekte çarpımın (en azından Python'da) O (n ^ 1.6) -zamanının uzunluğu sayı ve sayılar uzunluklarında O (log n) alanıdır.
Tuomas Laakkonen

@ThomasAhle Hayır, log (a ^ n) = n * log (a) böylece numarayı saklamak için O (l log k) alanına sahip olursunuz. Bu nedenle, l uzunluğunun bir listesi ve orijinal k uzunluğunun sayıları göz önüne alındığında, O (l) -uzayına sahip olursunuz, ancak sabit faktör (log k) hepsini yazmaktan daha düşük olacaktır. (Yöntemin soruyu cevaplamanın özellikle iyi bir yolu olduğunu düşünmüyorum.)
Tuomas Laakkonen

3

Q2 için bu, diğerlerinden biraz daha verimsiz bir çözümdür, ancak yine de O (N) çalışma süresine sahiptir ve O (k) alanı kaplar.

Fikir orijinal algoritmayı iki kez çalıştırmaktır. İlkinde eksik olan bir sayı elde edersiniz, bu da size eksik sayıların üst sınırını verir. Bu numarayı diyelim N. Eksik iki sayının toplanacağını biliyorsunuz N, bu yüzden ilk sayı sadece [1, floor((N-1)/2)]ikincisi girilecek aralıkta olabilir [floor(N/2)+1,N-1].

Böylece, ilk aralıkta yer almayan tüm sayıları atarak tüm sayıları tekrar döngüye alırsınız. Olanları, toplamlarını takip edersiniz. Son olarak, eksik iki sayıdan birini ve ikinci olarak uzantıyı bilirsiniz.

Bu yöntem genelleştirilebilir ve giriş üzerinde tek bir geçiş sırasında birden çok arama "paralel" çalıştırmak belki bir duygu var, ama henüz nasıl anlayamadım.


Ahaha evet bu Q2 için bulduğum aynı çözüm, sadece N / 2 altındaki tüm sayılar için negatifleri tekrar toplamı hesaplayarak , ama bu daha da iyi!
xjcl

2

Bunun karmaşık matematiksel denklemler ve teoriler olmadan yapılabileceğini düşünüyorum. Aşağıda, yerinde ve O (2n) zaman karmaşıklığı çözümü için bir teklif bulunmaktadır:

Girdi formu varsayımları:

torbadaki sayı sayısı = n

eksik sayı sayısı = k

Torbanın içindeki numaralar bir uzunluk n dizisiyle temsil edilir

Algo için giriş dizisinin uzunluğu = n

Dizideki eksik girişler (torbadan alınan numaralar) dizideki ilk öğenin değeri ile değiştirilir.

Örneğin. Başlangıçta torba [2,9,3,7,8,6,4,5,1,10] gibi görünüyor. 4 çıkarılırsa, 4 değeri 2 olur (dizinin ilk öğesi). Bu nedenle 4 tane çıkardıktan sonra çanta şöyle görünecektir [2,9,3,7,8,6,2,5,1,10]

Bu çözümün anahtarı, dizi geçilirken o INDEX'teki değeri reddederek ziyaret edilen bir sayının INDEX'ini etiketlemektir.

    IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers)
    {
        List<int> missingNumbers = new List<int>();
        int arrayLength = arrayOfNumbers.Length;

        //First Pass
        for (int i = 0; i < arrayLength; i++)
        {
            int index = Math.Abs(arrayOfNumbers[i]) - 1;
            if (index > -1)
            {
                arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes
            }
        }

        //Second Pass to get missing numbers
        for (int i = 0; i < arrayLength; i++)
        {                
            //If this index is unvisited, means this is a missing number
            if (arrayOfNumbers[i] > 0)
            {
                missingNumbers.Add(i + 1);
            }
        }

        return missingNumbers;
    }

Bu çok fazla bellek kullanıyor.
Thomas Ahle

2

Bunun gibi akış algoritmalarını genelleştirmenin genel bir yolu vardır. Fikir, köğeleri orijinal algoritmamızın sorunu bizim için çözdüğü bağımsız alt sorunlara umarım 'yaymak' için biraz rastgele kullanım kullanmaktır . Bu teknik, diğer şeylerin yanı sıra seyrek sinyal rekonstrüksiyonunda kullanılır.

  • aBoyut olarak bir dizi yapın u = k^2.
  • Herhangi seç evrensel karma işlevi , h : {1,...,n} -> {1,...,u}. ( Çarpma-kaydırma gibi )
  • Her biri için ide 1, ..., nartışa[h(i)] += i
  • xGiriş akışındaki her sayı için azaltın a[h(x)] -= x.

Eksik sayıların tümü farklı bölümlere ayrıldıysa, dizinin sıfır olmayan öğeleri artık eksik sayıları içerecektir.

Belirli bir çiftin aynı kepçeye gönderilme olasılığı, 1/uevrensel bir hash fonksiyonu tanımından daha azdır . Yaklaşık k^2/2çiftler olduğu için hata olasılığının en fazla olduğu durumdayız k^2/2/u=1/2. Yani, en az% 50 olasılıkla başardık ve arttırırsak uşansımızı arttırırız.

Bu algoritmanın alan k^2 lognbitleri aldığını unutmayın ( lognDizi dizisi başına bitlere ihtiyacımız var .) Bu, @Dimitris Andreou'nun cevabının gerektirdiği alanla eşleşir (Özellikle de rasgele hale getirilen polinom çarpanlarına ayırma gereksinimi.) Bu algoritma ayrıca sabittir kgüç toplamları durumunda zaman yerine güncelleme başına süre .

Aslında, yorumlarda açıklanan numarayı kullanarak güç toplamı yönteminden daha verimli olabiliriz.


Not: Makinemizde daha xorhızlıysa, her kovada da kullanabiliriz sum.
Thomas Ahle

İlginç ama bence bu sadece uzay kısıtlamasına saygı duyuyor k <= sqrt(n)- en azından u=k^2? Diyelim ki k = 11 ve n = 100, o zaman 121 kovaya sahip olacaksınız ve algoritma, her # akışından okurken kontrol ettiğiniz 100 bitlik bir diziye sahip olacaksınız. Artırmak ubaşarı şansını artırır, ancak alan sınırını aşmadan önce ne kadar artırabileceğinizin bir sınırı vardır.
FuzzyTree

1
Sorun, bence nçok daha mantıklı geliyor k, ama aslında k lognsürekli zaman güncellemeleri olsa da, açıklanan hash çok benzer bir yöntemle yer alabilirsiniz . Güçler yöntemi gibi gnunet.org/eppstein-set-reconciliation adresinde açıklanmıştır , ancak temelde, bazı kovaların yalnızca bir öğeye sahip olacağını garanti eden, tablolama karma gibi güçlü bir karma işlevine sahip 'k' kovalarına hash edersiniz. . Kodunu çözmek için, bu kovayı tanımlar ve öğeyi her iki kovadan kaldırır, bu da (muhtemelen) başka bir kovayı serbest bırakır vb.
Thomas Ahle

2

Kimsenin cevap vermediğine şaşırdığım Q2'ye çok basit bir çözüm. İki eksik sayının toplamını bulmak için Q1'in yöntemini kullanın. Bunu S ile gösterelim, o zaman eksik sayılardan biri S / 2'den diğeri S / 2'den (duh) daha büyüktür. 1'den S / 2'ye kadar olan tüm sayıları toplayın ve eksik sayılar arasında daha düşük olanı bulmak için formülün sonucuyla (Q1'deki yönteme benzer şekilde) karşılaştırın. Daha büyük eksik numarayı bulmak için S'den çıkarın.


Bence bu Svalorzen'in cevabı ile aynı , ama siz daha iyi kelimelerle açıkladınız. Qk'ya nasıl genelleştireceğiniz hakkında bir fikriniz var mı?
John McClane

Diğer cevabı kaçırdığım için üzgünüm. Bu durumda $ Q_k $ için genelleştirmenin mümkün olup olmadığından emin değilim, çünkü bu durumda en küçük eksik öğeyi bir aralığa bağlayamazsınız. Bazı öğelerin $ S / k $ 'dan küçük olması gerektiğini biliyorsunuz, ancak bu birden çok öğe için geçerli olabilir
Gilad Deutsch

1

Çok güzel bir problem. Qk için belirli bir fark kullanmak için giderdim. Ruby gibi birçok programlama dilinin de desteği var:

missing = (1..100).to_a - bag

Muhtemelen en verimli çözüm değildir, ancak bu durumda böyle bir görevle (bilinen sınırlar, düşük sınırlar) karşılaşırsam gerçek hayatta kullanacağım bir çözümdür. Sayı kümesi çok büyük olursa, elbette daha verimli bir algoritma düşünürdüm, ancak o zamana kadar basit çözüm benim için yeterli olurdu.


1
Bu çok fazla alan kullanıyor.
Thomas Ahle

@ThomasAhle: Neden her ikinci cevaba gereksiz yorumlar ekliyorsunuz? Çok fazla alan kullanmakla ne demek istiyorsun?
DarkDust

Çünkü soru "N ile orantılı herhangi bir ek alan göze alamaz." Bu çözüm tam olarak bunu yapıyor.
Thomas Ahle

1

Bir Bloom Filtresi kullanmayı deneyebilirsiniz . Torbanın içindeki her sayıyı çiçeklenmeye sokun, daha sonra bulunmayan her birini raporlayana kadar tam 1-k setini tekrarlayın. Bu, tüm senaryolarda cevabı bulamayabilir, ancak yeterince iyi bir çözüm olabilir.


Ayrıca silmeye izin veren sayma çiçek filtresi de vardır. Ardından tüm sayıları ekleyebilir ve akışta gördüklerinizi silebilirsiniz.
Thomas Ahle

Haha bu muhtemelen daha pratik cevaplardan biri, ama çok az dikkat çekiyor.
ldog

1

Bu soruya farklı bir yaklaşımda bulunup, çözmeye çalıştığı daha büyük sorun hakkında daha fazla ayrıntı için görüşmeciyi inceleyeceğim. Soruna ve onu çevreleyen gereksinimlere bağlı olarak, bariz set tabanlı çözüm doğru şey olabilir ve daha sonra bir liste-ve-pick-yoluyla-oluştur yaklaşımı olmayabilir.

Örneğin, görüşmeci nmesaj gönderecek ve bunu bilmesi gerekebilir . Bundan sonra, set eksik elemanların listesini içerir ve yapılacak ek işlem yoktur.k bunun bir yanıtla sonuçlanmadığını bilmeli ve bunu mümkün olduğunca az duvar saati zamanında bilmelidirn-k . Ayrıca, mesaj kanalının doğasının tam delikte çalışsa bile, son yanıt geldikten sonra son sonucun üretilmesinin ne kadar süreceği üzerinde herhangi bir etkisi olmadan mesajlar arasında biraz işlem yapmak için yeterli zaman olduğunu söyleyelim. Bu süre, gönderilen her mesajın tanımlayıcı bir yüzünün bir kümeye eklenmesi ve karşılık gelen her yanıt geldiğinde silinmesi için kullanılabilir. Son cevap geldiğinde, yapılacak tek şey tanımlayıcıyı setten çıkarmaktır.O(log k+1)k

Önceden oluşturulmuş sayı çuvallarını toplu işleme için kesinlikle en hızlı yaklaşım bu değildir, çünkü her şey çalışır O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k)). Ancak herhangi bir değeri için çalışır k(vaktinden önce bilinmese bile) ve yukarıdaki örnekte en kritik aralığı en aza indirecek şekilde uygulanmıştır.


Yalnızca O (k ^ 2) fazladan belleğiniz varsa bu işe yarar mı?
Thomas Ahle

1

Çözümü simetriler (gruplar, matematik dilinde) açısından düşünerek motive edebilirsiniz. Sayı kümesinin sırası ne olursa olsun, cevap aynı olmalıdır. kEksik öğeleri belirlemeye yardımcı olması için işlevleri kullanacaksanız , hangi işlevlerin bu özelliğe sahip olduğunu düşünmelisiniz: simetrik. İşlev s_1(x) = x_1 + x_2 + ... + x_nsimetrik bir işleve bir örnektir, ancak daha yüksek dereceli diğerleri vardır. Özellikle, temel simetrik fonksiyonları göz önünde bulundurun . Derece 2'nin temel simetrik işlevi s_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n, iki elementin tüm ürünlerinin toplamıdır. Benzer şekilde derece 3 ve üzerindeki temel simetrik fonksiyonlar için. Açıkçası simetriktirler. Ayrıca, tüm simetrik fonksiyonlar için yapı taşları oldukları ortaya çıkıyor.

Temel simetrik fonksiyonları, ilerledikçe not ederek oluşturabilirsiniz s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1)). Daha fazla düşünce sizi ikna etmeli s_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1)), böylece bir geçişte hesaplanabilirler.

Dizide hangi öğelerin eksik olduğunu nasıl anlarız? Polinomu düşünün (z-x_1)(z-x_2)...(z-x_n). 0Herhangi bir sayı koyarsanız değerlendirir x_i. Polinomu genişleterek elde edersiniz z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n. Temel simetrik fonksiyonlar da burada görünür, bu gerçekten şaşırtıcı değildir, çünkü köklere herhangi bir permütasyon uygularsak polinom aynı kalmalıdır.

Bu yüzden polinomu inşa edebiliriz ve diğerlerinin de belirttiği gibi sette hangi sayıların olmadığını anlamak için faktörü hesaplamaya çalışabiliriz.

Son olarak, büyük sayılarla (n. Simetrik polinom sırayla olacaktır 100!) taşan hafızayla ilgili endişelerimiz varsa , bu hesaplamaları 100'den büyük bir asal olan mod pyerlerde yapabiliriz. Bu pdurumda polinomu değerlendirir mod pve tekrar değerlendirdiğini buluruz için 0giriş kümesindeki bir sayıdır, ve giriş, bir sayı kümesinde olmadığı durumlarda, sıfır olmayan bir değere değerlendirir. Bununla birlikte, diğerlerinin de belirttiği gibi, değerleri polinomdan zaman içinde çıkarmak için kdeğil N, polinomu hesaba katmak zorundayız mod p.


1

Yine başka bir yol, artık grafik filtrelemeyi kullanmaktır.

1'den 4'e kadar rakamlarımız olduğunu ve 3'ün eksik olduğunu varsayalım. İkili gösterim aşağıdaki gibidir,

1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b

Ve aşağıdaki gibi bir akış grafiği oluşturabilirim.

                   1
             1 -------------> 1
             |                | 
      2      |     1          |
0 ---------> 1 ----------> 0  |
|                          |  |
|     1            1       |  |
0 ---------> 0 ----------> 0  |
             |                |
      1      |      1         |
1 ---------> 0 -------------> 1

Akış grafiğinin x düğümü içerdiğini, x'in bit sayısı olduğunu unutmayın. Ve maksimum kenar sayısı (2 * x) -2'dir.

32 bit tamsayı için O (32) veya O (1) boşluk alacaktır.

Şimdi 1,2,4'ten başlayarak her sayı için kapasiteyi kaldırırsam, artık bir grafik bırakılır.

0 ----------> 1 ---------> 1

Sonunda aşağıdaki gibi bir döngü çalıştıracağım,

 result = []
 for x in range(1,n):
     exists_path_in_residual_graph(x)
     result.append(x)

Şimdi sonuç da resulteksik olmayan sayılar içeriyor (yanlış pozitif). Ancak eksik elemanlar olduğunda k <= (sonucun boyutu) <= nk .

Sonucu eksik veya işaretlemek için son bir kez verilen listeden geçeceğim.

Böylece zaman karmaşıklığı O (n) olacaktır.

Son olarak, düğümler alarak Yanlış pozitif (ve gerekli boşluk) sayısını azaltmak mümkündür 00, 01, 11, 10bunun yerine sadece bir 0ve 1.


Grafik diyagramınızı anlamıyorum. Düğümler, kenarlar ve sayılar neyi temsil eder? Neden bazı kenarlar diğerlerine değil, yönlendirilmiş?
dain

Aslında cevabınızı gerçekten anlamıyorum, biraz daha açıklığa kavuşturabilir misiniz?
dain

1

Muhtemelen O (k) 'nin ne anlama geldiğine ilişkin açıklığa ihtiyacınız vardır.

İşte keyfi k için önemsiz bir çözüm: sayı kümenizdeki her v için 2 ^ v toplamını biriktirin. Sonunda, i'yi 1'den N'ye döngü. 2 ^ i ile bitsel ANDed toplamı sıfırsa, i eksiktir. (Sayısal olarak, toplamın zemininin 2 ^ i'ye bölünmesi eşitse. Veya sum modulo 2^(i+1)) < 2^i.)

Kolay değil mi? O (N) süresi, O (1) depolama ve keyfi k'yi destekler.

Bunun dışında, gerçek bir bilgisayarda O (N) alan gerektirecek çok büyük sayılar hesaplıyorsunuz. Aslında, bu çözüm bir bit vektörü ile aynıdır.

Böylece akıllı olabilir ve toplamı ve karelerin toplamını ve küplerin toplamını ... v ^ k toplamına kadar hesaplayabilir ve sonucu elde etmek için süslü matematik yapabilirsiniz. Ama bunlar da büyük rakamlar, şu soruyu akla getiriyor: hangi soyut operasyon modelinden bahsediyoruz? O (1) boşluğuna ne kadar sığar ve ihtiyacınız olan boyutta sayıları toplamak ne kadar sürer?


Güzel cevap! Küçük bir şey: "sum modulo 2 ^ i sıfır ise, o zaman eksik" yanlıştır. Ama neyin amaçlandığı açık. "Toplam modulo 2 ^ (i + 1) 2 ^ i daha az ise, o zaman eksik" doğru olacağını düşünüyorum. (Tabii ki, çoğu programlama dilinde modulo hesaplaması yerine bit kaydırma kullanırdık. Bazen programlama dilleri her zamanki matematik notasyonundan biraz daha etkileyici olur. :-))
jcsahnwaldt, GoFundMonica

1
Teşekkürler, tamamen haklısın! Tembel ve matematiksel notasyondan sapmış olmama rağmen düzeltildi ... oh, ve ben de bunu berbat ettim. Tekrar sabitleniyor ...
18:17 sfink

1

İşte sdcvvc / Dimitris Andreou'nun cevaplarının yaptığı gibi karmaşık matematiğe dayanmayan bir çözüm, giriş dizisini caf ve Albay Panic gibi değiştirmedi ve Chris Lercher, JeremyP ve diğerleri yaptı. Temel olarak, Svalorzen / Gilad Deutch'ın Q2 fikri ile başladım, Qk genel durumuna genelleştirdim ve algoritmanın çalıştığını kanıtlamak için Java'da uyguladım.

Fikir

Biz keyfi bir aralık olduğunu varsayalım I biz sadece eksik sayıların en az birini içerdiğini biliyoruz ki. Giriş dizi boyunca bir geçiş sonrası, sadece gelen numaraları bakarak I , biz toplamı her ikisi de elde edebilir S ve miktarı Q numaraları eksik I . Biz, sadece azaltılmasıyla bunun I'in uzunluğu biz bir dizi sorunla her I (elde edilmesi için Q ) ve tüm sayıların önceden hesaplanan toplamı azaltarak I bu karşılaşılan numarası (elde etmek için her defasında S ).

Şimdi S ve Q'ya bakıyoruz . Eğer Q = 1 , o zaman anlamına gelir Ben sadece içerdiği eksik sayıların biri ve bu sayı açıkça S . Biz işaretlemek ben bitmiş gibi (o programda "kesin" olarak adlandırılır) ve daha fazla dikkate dışarıda bırakın. Diğer taraftan, eğer Q> 1 ise , I'de bulunan eksik sayıların ortalama A = S / Q'unu hesaplayabiliriz . Tüm sayılar farklı olduğu için, bu sayılardan en az biri kesinlikle A'dan küçüktür ve en az biri kesinlikle A'dan büyüktür . Şimdi bölünmüş I içinde Aher biri en az bir eksik sayı içeren iki küçük aralığa bölünür. Bir tamsayı olması durumunda A'yı hangi aralıklarla atadığımızın bir önemi olmadığını unutmayın .

Aralıkların her biri için ayrı ayrı (fakat aynı geçişte) S ve Q hesaplayan bir sonraki dizi geçişini yaparız ve bundan sonra Q = 1 ile işaretleme aralıkları ve Q> 1 ile ayırma aralıkları yaparız . Yeni "belirsiz" aralık kalmayıncaya kadar bu sürece devam ediyoruz, yani bölünecek hiçbir şeyimiz yok çünkü her aralık tam olarak bir eksik sayı içeriyor (ve S'yi bildiğimiz için bu sayıyı her zaman biliyoruz ). Mümkün olan tüm sayıları içeren tek "bütün aralık" aralığından başlıyoruz (örneğin [1..N] gibi ).

Zaman ve mekan karmaşıklığı analizi

İşlem duruncaya kadar yapmamız gereken toplam geçiş sayısı p hiçbir zaman eksik sayılar k'dan daha fazla olamaz . P <= k eşitsizliği titizlikle kanıtlanabilir. Öte yandan, büyük k değerleri için yararlı olan ampirik bir üst sınır p <log 2 N + 3 de vardır . Ait olduğu aralığı belirlemek için giriş dizisinin her sayısı için ikili bir arama yapmamız gerekir. Bu , zaman karmaşıklığına günlük k çarpanını ekler .

Toplamda, zaman karmaşıklığı O (N ᛫ dk (k, log N) ᛫ log k) 'dır . Büyük k için , bunun sdcvvc / Dimitris Andreou'nun O (N ᛫ k) yönteminden önemli ölçüde daha iyi olduğuna dikkat edin .

Çalışması için, algoritma "bitset" çözeltilerinde O (k) 'dan fazla k aralıkta depolamak için ek alan gerektirir , bu da O (N)' den çok daha iyidir .

Java uygulaması

Yukarıdaki algoritmayı uygulayan bir Java sınıfı. Her zaman sıralanmış bir dizi eksik sayı döndürür . Bunun yanı sıra, eksik sayıların k sayısını gerektirmez çünkü ilk geçişte hesaplar. Bütün sayı aralığı minNumberve maxNumberparametreleri tarafından verilir (örneğin, sorudaki ilk örnek için 1 ve 100).

public class MissingNumbers {
    private static class Interval {
        boolean ambiguous = true;
        final int begin;
        int quantity;
        long sum;

        Interval(int begin, int end) { // begin inclusive, end exclusive
            this.begin = begin;
            quantity = end - begin;
            sum = quantity * ((long)end - 1 + begin) / 2;
        }

        void exclude(int x) {
            quantity--;
            sum -= x;
        }
    }

    public static int[] find(int minNumber, int maxNumber, NumberBag inputBag) {
        Interval full = new Interval(minNumber, ++maxNumber);
        for (inputBag.startOver(); inputBag.hasNext();)
            full.exclude(inputBag.next());
        int missingCount = full.quantity;
        if (missingCount == 0)
            return new int[0];
        Interval[] intervals = new Interval[missingCount];
        intervals[0] = full;
        int[] dividers = new int[missingCount];
        dividers[0] = minNumber;
        int intervalCount = 1;
        while (true) {
            int oldCount = intervalCount;
            for (int i = 0; i < oldCount; i++) {
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    if (itv.quantity == 1) // number inside itv uniquely identified
                        itv.ambiguous = false;
                    else
                        intervalCount++; // itv will be split into two intervals
            }
            if (oldCount == intervalCount)
                break;
            int newIndex = intervalCount - 1;
            int end = maxNumber;
            for (int oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
                // newIndex always >= oldIndex
                Interval itv = intervals[oldIndex];
                int begin = itv.begin;
                if (itv.ambiguous) {
                    // split interval itv
                    // use floorDiv instead of / because input numbers can be negative
                    int mean = (int)Math.floorDiv(itv.sum, itv.quantity) + 1;
                    intervals[newIndex--] = new Interval(mean, end);
                    intervals[newIndex--] = new Interval(begin, mean);
                } else
                    intervals[newIndex--] = itv;
                end = begin;
            }
            for (int i = 0; i < intervalCount; i++)
                dividers[i] = intervals[i].begin;
            for (inputBag.startOver(); inputBag.hasNext();) {
                int x = inputBag.next();
                // find the interval to which x belongs
                int i = java.util.Arrays.binarySearch(dividers, 0, intervalCount, x);
                if (i < 0)
                    i = -i - 2;
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    itv.exclude(x);
            }
        }
        assert intervalCount == missingCount;
        for (int i = 0; i < intervalCount; i++)
            dividers[i] = (int)intervals[i].sum;
        return dividers;
    }
}

Adalet için, bu sınıf NumberBagnesne şeklinde girdi alır . NumberBagdizi değişikliğine ve rastgele erişime izin vermez ve dizinin ardışık geçiş için kaç kez istendiğini sayar. Aynı zamanda büyük dizi testi için Iterable<Integer>, ilkel intdeğerlerin boksundan kaçınması ve int[]uygun bir test hazırlığı için büyük bir kısmının sarılmasına izin vermesinden daha uygundur. İstenirse, yerine zor değil NumberBagtarafından int[]veya Iterable<Integer>yazdığınız findforeach olanları içine onun içinde iki-döngüler değiştirerek, imza.

import java.util.*;

public abstract class NumberBag {
    private int passCount;

    public void startOver() {
        passCount++;
    }

    public final int getPassCount() {
        return passCount;
    }

    public abstract boolean hasNext();

    public abstract int next();

    // A lightweight version of Iterable<Integer> to avoid boxing of int
    public static NumberBag fromArray(int[] base, int fromIndex, int toIndex) {
        return new NumberBag() {
            int index = toIndex;

            public void startOver() {
                super.startOver();
                index = fromIndex;
            }

            public boolean hasNext() {
                return index < toIndex;
            }

            public int next() {
                if (index >= toIndex)
                    throw new NoSuchElementException();
                return base[index++];
            }
        };
    }

    public static NumberBag fromArray(int[] base) {
        return fromArray(base, 0, base.length);
    }

    public static NumberBag fromIterable(Iterable<Integer> base) {
        return new NumberBag() {
            Iterator<Integer> it;

            public void startOver() {
                super.startOver();
                it = base.iterator();
            }

            public boolean hasNext() {
                return it.hasNext();
            }

            public int next() {
                return it.next();
            }
        };
    }
}

Testler

Bu sınıfların kullanımını gösteren basit örnekler aşağıda verilmiştir.

import java.util.*;

public class SimpleTest {
    public static void main(String[] args) {
        int[] input = { 7, 1, 4, 9, 6, 2 };
        NumberBag bag = NumberBag.fromArray(input);
        int[] output = MissingNumbers.find(1, 10, bag);
        System.out.format("Input: %s%nMissing numbers: %s%nPass count: %d%n",
                Arrays.toString(input), Arrays.toString(output), bag.getPassCount());

        List<Integer> inputList = new ArrayList<>();
        for (int i = 0; i < 10; i++)
            inputList.add(2 * i);
        Collections.shuffle(inputList);
        bag = NumberBag.fromIterable(inputList);
        output = MissingNumbers.find(0, 19, bag);
        System.out.format("%nInput: %s%nMissing numbers: %s%nPass count: %d%n",
                inputList, Arrays.toString(output), bag.getPassCount());

        // Sieve of Eratosthenes
        final int MAXN = 1_000;
        List<Integer> nonPrimes = new ArrayList<>();
        nonPrimes.add(1);
        int[] primes;
        int lastPrimeIndex = 0;
        while (true) {
            primes = MissingNumbers.find(1, MAXN, NumberBag.fromIterable(nonPrimes));
            int p = primes[lastPrimeIndex]; // guaranteed to be prime
            int q = p;
            for (int i = lastPrimeIndex++; i < primes.length; i++) {
                q = primes[i]; // not necessarily prime
                int pq = p * q;
                if (pq > MAXN)
                    break;
                nonPrimes.add(pq);
            }
            if (q == p)
                break;
        }
        System.out.format("%nSieve of Eratosthenes. %d primes up to %d found:%n",
                primes.length, MAXN);
        for (int i = 0; i < primes.length; i++)
            System.out.format(" %4d%s", primes[i], (i % 10) < 9 ? "" : "\n");
    }
}

Büyük dizi testi şu şekilde yapılabilir:

import java.util.*;

public class BatchTest {
    private static final Random rand = new Random();
    public static int MIN_NUMBER = 1;
    private final int minNumber = MIN_NUMBER;
    private final int numberCount;
    private final int[] numbers;
    private int missingCount;
    public long finderTime;

    public BatchTest(int numberCount) {
        this.numberCount = numberCount;
        numbers = new int[numberCount];
        for (int i = 0; i < numberCount; i++)
            numbers[i] = minNumber + i;
    }

    private int passBound() {
        int mBound = missingCount > 0 ? missingCount : 1;
        int nBound = 34 - Integer.numberOfLeadingZeros(numberCount - 1); // ceil(log_2(numberCount)) + 2
        return Math.min(mBound, nBound);
    }

    private void error(String cause) {
        throw new RuntimeException("Error on '" + missingCount + " from " + numberCount + "' test, " + cause);
    }

    // returns the number of times the input array was traversed in this test
    public int makeTest(int missingCount) {
        this.missingCount = missingCount;
        // numbers array is reused when numberCount stays the same,
        // just Fisher–Yates shuffle it for each test
        for (int i = numberCount - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            if (i != j) {
                int t = numbers[i];
                numbers[i] = numbers[j];
                numbers[j] = t;
            }
        }
        final int bagSize = numberCount - missingCount;
        NumberBag inputBag = NumberBag.fromArray(numbers, 0, bagSize);
        finderTime -= System.nanoTime();
        int[] found = MissingNumbers.find(minNumber, minNumber + numberCount - 1, inputBag);
        finderTime += System.nanoTime();
        if (inputBag.getPassCount() > passBound())
            error("too many passes (" + inputBag.getPassCount() + " while only " + passBound() + " allowed)");
        if (found.length != missingCount)
            error("wrong result length");
        int j = bagSize; // "missing" part beginning in numbers
        Arrays.sort(numbers, bagSize, numberCount);
        for (int i = 0; i < missingCount; i++)
            if (found[i] != numbers[j++])
                error("wrong result array, " + i + "-th element differs");
        return inputBag.getPassCount();
    }

    public static void strideCheck(int numberCount, int minMissing, int maxMissing, int step, int repeats) {
        BatchTest t = new BatchTest(numberCount);
        System.out.println("╠═══════════════════════╬═════════════════╬═════════════════╣");
        for (int missingCount = minMissing; missingCount <= maxMissing; missingCount += step) {
            int minPass = Integer.MAX_VALUE;
            int passSum = 0;
            int maxPass = 0;
            t.finderTime = 0;
            for (int j = 1; j <= repeats; j++) {
                int pCount = t.makeTest(missingCount);
                if (pCount < minPass)
                    minPass = pCount;
                passSum += pCount;
                if (pCount > maxPass)
                    maxPass = pCount;
            }
            System.out.format("║ %9d  %9d  ║  %2d  %5.2f  %2d  ║  %11.3f    ║%n", missingCount, numberCount, minPass,
                    (double)passSum / repeats, maxPass, t.finderTime * 1e-6 / repeats);
        }
    }

    public static void main(String[] args) {
        System.out.println("╔═══════════════════════╦═════════════════╦═════════════════╗");
        System.out.println("║      Number count     ║      Passes     ║  Average time   ║");
        System.out.println("║   missimg     total   ║  min  avg   max ║ per search (ms) ║");
        long time = System.nanoTime();
        strideCheck(100, 0, 100, 1, 20_000);
        strideCheck(100_000, 2, 99_998, 1_282, 15);
        MIN_NUMBER = -2_000_000_000;
        strideCheck(300_000_000, 1, 10, 1, 1);
        time = System.nanoTime() - time;
        System.out.println("╚═══════════════════════╩═════════════════╩═════════════════╝");
        System.out.format("%nSuccess. Total time: %.2f s.%n", time * 1e-9);
    }
}

Onları Ideone'de deneyin


0

Ben keyfi olarak büyük tamsayılar için ve fonksiyonları olduğu göz önüne alındığında, bir O(k)zaman ve O(log(k))uzay algoritması var inanıyorum :floor(x)log2(x)

Eklediğiniz bir kbit uzunluğunda tamsayı (dolayısıyla log8(k)boşluk) vardır; x^2burada x, çantada bulduğunuz bir sonraki sayıdır: s=1^2+2^2+...Bu O(N)zaman alır (bu, görüşmeci için bir sorun değildir). Sonunda aradığınız j=floor(log2(s))en büyük sayı olan olsun . Sonra s=s-jtekrar yukarıdakileri yaparsınız:

for (i = 0 ; i < k ; i++)
{
  j = floor(log2(s));
  missing[i] = j;
  s -= j;
}

Şimdi, 2756-bit tamsayıları için genellikle floor ve log2 işlevleriniz yoktur, bunun yerine çiftler içindir. Yani? Basitçe, her 2 bayt (veya 1 veya 3 veya 4) için bu işlevleri istediğiniz sayıları elde etmek için kullanabilirsiniz, ancak bu O(N)zaman karmaşıklığına bir faktör ekler


0

Bu kulağa aptalca gelebilir, ancak size sunulan ilk problemde, bu denklemi kullanarak eksik sayıyı bulmak için onları toplamak için torbada kalan tüm sayıları görmeniz gerekir.

Yani, tüm sayıları görebildiğiniz için, eksik olan numaraya bakın. İki sayı eksik olduğunda da aynı şey geçerlidir. Bence oldukça basit. Çantada kalan sayıları gördüğünüzde denklem kullanmanın bir anlamı yok.


2
Onları özetlemenin yararı, daha önce hangi sayıları gördüğünüzü hatırlamak zorunda olmamanızdır (örneğin, fazladan bellek gereksinimi yoktur). Aksi takdirde, tek seçenek, görülen tüm değerlerin bir kümesini korumak ve daha sonra eksik olanı bulmak için bu kümenin üzerinde tekrarlamaktır.
Dan Tao

3
Bu soru genellikle O (1) uzay karmaşıklığının koşuluyla sorulur.

İlk N sayılarının toplamı N (N + 1) / 2'dir. N = 100 için, Toplam = 100 * (101) / 2 = 5050;
Mart

0

Bunun şu şekilde genelleştirilebileceğini düşünüyorum:

S, M'yi aritmetik seri ve çarpma toplamı için başlangıç ​​değerleri olarak belirtin.

S = 1 + 2 + 3 + 4 + ... n=(n+1)*n/2
M = 1 * 2 * 3 * 4 * .... * n 

Bunu hesaplamak için bir formül düşünmeliyim, ama mesele bu değil. Her neyse, bir numara eksikse, çözümü zaten sağladınız. Ancak, iki sayı eksikse, yeni toplamı ve toplam katını S1 ve M1 ile gösterelim, bu aşağıdaki gibi olacaktır:

S1 = S - (a + b)....................(1)

Where a and b are the missing numbers.

M1 = M - (a * b)....................(2)

S1, M1, M ve S'yi bildiğinizden, yukarıdaki denklem eksik sayılar olan a ve b'yi bulmak için çözülebilir.

Şimdi eksik olan üç sayı için:

S2 = S - ( a + b + c)....................(1)

Where a and b are the missing numbers.

M2 = M - (a * b * c)....................(2)

Şimdi bilinmeyen 3, çözebileceğiniz iki denklem var.


Çarpma oldukça büyür .. Ayrıca, 2'den fazla eksik sayı genelleme nasıl?
Thomas Ahle

Bu formülleri N = 3 ve eksik sayılar = {1, 2} ile çok basit bir sırayla denedim. Hatanın okuması gereken formüllerde (2) olduğuna inandığım için çalışmadım M1 = M / (a * b)( bu cevaba bakınız ). Sonra iyi çalışıyor.
dma_k

0

Bunun etkili olup olmadığını bilmiyorum ama bu çözümü önermek istiyorum.

  1. 100 öğenin xor değerini hesaplama
  2. 98 öğenin xor değerini hesaplayın (2 öğe kaldırıldıktan sonra)
  3. Şimdi (1 sonuç) XOR (2 sonuç) a ve b eksik elemanlar ise nos i..ea XOR b eksik size iki xor verir
    Her zamanki yaklaşım ile eksik No. toplamını 4.Get toplam formül fark ve fark d diyelim.

Şimdi, her ikisi de [1, 100] 'de bulunan ve d ile toplanan olası çiftleri (p, q) elde etmek için bir döngü çalıştırın.

Bir çift elde edildiğinde (3 sonucunun) XOR p = q olup olmadığını kontrol edin ve evet ise tamamlandık.

Yanılıyorsam lütfen beni düzeltin ve eğer bu doğruysa zaman karmaşıklığı hakkında yorum yapın


2
Toplamın ve x'in benzersiz olarak iki sayıyı tanımladığını sanmıyorum. D ile toplanan tüm olası k-tupl'leri elde etmek için bir döngü çalıştırmak O (C (n, k-1)) = O (n <sup> k-1 </sup>) alır, ki bu k> 2 için, kötüdür.
Teepeemm

0

Biz yapabilir Q1 ve Q2 olarak O (günlük n) çoğu zaman.

Bizim varsayalım memory chipdizisi oluşur nsayısı test tubes. Ve xtest tüpündeki bir sayı x milliliterkimyasal-sıvı ile temsil edilir .

İşlemcimizin bir olduğunu varsayalım laser light. Lazeri yaktığımızda, tüm tüpleri boyuna dik olarak hareket ettirir. Kimyasal sıvıdan her geçtiğinde parlaklık azalır 1. Ve ışığın belirli mililitre işaretinden geçirilmesi bir işlemdir O(1).

Şimdi lazerimizi test tüpünün ortasında yakıp aydınlık verirsek

  • önceden hesaplanmış bir değere eşittir (hiçbir sayı eksik olmadığında hesaplanır), o zaman eksik sayılar büyüktür n/2.
  • Çıktımız daha küçükse, en az bir eksik sayı daha küçüktür n/2. Parlaklığın 1veya tarafından azaltılıp azaltılmadığını da kontrol edebiliriz 2. azalırsa, 1bir eksik sayı n/2diğerinden küçüktür n/2. Eğer azalırsa, 2her iki sayı da küçüktür n/2.

Yukarıdaki işlemi tekrar tekrar sorun alanımızı daraltarak tekrarlayabiliriz. Her adımda, etki alanını yarı yarıya küçültüriz. Sonunda sonucumuza ulaşabiliriz.

Bahsetmeye değer paralel algoritmalar (ilginç oldukları için),

  • bazı paralel algoritmalara göre sıralama, örneğin, paralel birleştirme zaman içinde yapılabilir O(log^3 n). Ve sonra eksik sayı O(log n)zaman içinde ikili arama ile bulunabilir .
  • Teorik olarak, nişlemcilerimiz varsa, her işlem girişlerden birini kontrol edebilir ve sayıyı tanımlayan bir bayrak ayarlayabilir (uygun olarak bir dizide). Ve bir sonraki adımda her işlem her bayrağı kontrol edebilir ve son olarak işaretlenmemiş sayıyı çıktılayabilir. Tüm süreç zaman alacaktır O(1). Ek O(n)alan / bellek gereksinimi vardır.

Not, bu açıklamada belirtildiği gibi, yukarıda verilen iki paralel algoritmalar ek alanı gerekebilir .


Test tüpü lazer yöntemi gerçekten ilginç olsa da, umarım donanım talimatlarına iyi bir şekilde dönüşmediğini ve O(logn)bir bilgisayarda olması pek olası değildir .
SirGuy

1
Sıralama yönteminize gelince, bu, Ndaha iyi yapmak istediğimiz O(N)zamana ve zamana (bağımlılığı açısından N) fazladan fazladan alan gerektirecektir .
SirGuy

@SirGuy Test tüpü konsepti ve paralel işlem bellek maliyeti ile ilgili endişelerinizi takdir ediyorum. Benim görevim sorun hakkındaki düşüncelerimi paylaşmak. GPU işlemcileri artık paralel işlemeyi mümkün kılmaktadır. Kim bilir, eğer tüp konsepti gelecekte mevcut olmayacaksa.
shuva
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.