La domanda del colloquio facile è diventata più difficile: dati i numeri 1..100, trova il numero mancante dato esattamente k mancano


1146

Ho avuto un'interessante esperienza di colloquio di lavoro qualche tempo fa. La domanda è iniziata davvero semplice:

Q1 : Abbiamo un sacchetto contenente i numeri 1, 2, 3, ..., 100. Ogni numero appare esattamente una volta, quindi ci sono 100 numeri. Ora un numero viene estratto casualmente dalla borsa. Trova il numero mancante.

Ho sentito questa domanda di intervista prima, ovviamente, quindi ho risposto molto rapidamente sulla falsariga di:

A1 : Bene, la somma dei numeri 1 + 2 + 3 + … + Nè (N+1)(N/2)(vedi Wikipedia: somma delle serie aritmetiche ). Per N = 100, la somma è 5050.

Pertanto, se tutti i numeri sono presenti nel sacchetto, la somma sarà esattamente 5050. Poiché manca un numero, la somma sarà inferiore a questa e la differenza è quel numero. Quindi possiamo trovare quel numero mancante nel O(N)tempo e O(1)nello spazio.

A questo punto pensavo di aver fatto bene, ma all'improvviso la domanda prese una svolta inaspettata:

D2 : È corretto, ma ora come faresti se mancassero DUE numeri?

Non avevo mai visto / sentito / considerato questa variazione prima, quindi sono andato nel panico e non sono riuscito a rispondere alla domanda. L'intervistatore ha insistito per conoscere il mio processo di pensiero, quindi ho detto che forse possiamo ottenere più informazioni confrontandole con il prodotto previsto, o forse facendo un secondo passaggio dopo aver raccolto alcune informazioni dal primo passaggio, ecc., Ma stavo davvero girando al buio piuttosto che in realtà avere un percorso chiaro per la soluzione.

L'intervistatore ha cercato di incoraggiarmi dicendo che avere una seconda equazione è davvero un modo per risolvere il problema. A questo punto ero un po 'sconvolto (per non conoscere la risposta in anticipo) e ho chiesto se si tratta di una tecnica di programmazione generale (leggi: "utile") o se è solo una risposta trabocchetto / gotcha.

La risposta dell'intervistatore mi ha sorpreso: puoi generalizzare la tecnica per trovare 3 numeri mancanti. In effetti, puoi generalizzarlo per trovare k numeri mancanti.

Dk : Se nella borsa mancano esattamente k numeri, come lo troveresti in modo efficiente?

Questo è stato alcuni mesi fa e non riuscivo ancora a capire quale fosse questa tecnica. Ovviamente c'è un Ω(N)tempo limite inferiore dal momento che dobbiamo analizzare tutti i numeri almeno una volta, ma l'intervistatore insistito che il TEMPO e SPAZIO complessità della tecnica solving (meno lo O(N)scansione time input) è definito in k non N .

Quindi la domanda qui è semplice:

  • Come risolveresti il secondo trimestre ?
  • Come risolveresti il terzo trimestre ?
  • Come risolveresti Qk ?

chiarimenti

  • Generalmente ci sono N numeri da 1 .. N , non solo 1..100.
  • Non sto cercando l'ovvia soluzione basata su set, ad esempio utilizzando un set di bit , codificando la presenza / assenza di ciascun numero in base al valore di un bit designato, quindi utilizzando O(N)bit nello spazio aggiuntivo. Non possiamo permetterci alcun proporzionale spazio aggiuntivo per N .
  • Inoltre non sto cercando l'ovvio approccio di tipo first-first. Questo e l'approccio basato sul set meritano di essere menzionati in un'intervista (sono facili da implementare e, a seconda di N , possono essere molto pratici). Sto cercando la soluzione del Santo Graal (che può essere o meno pratica da implementare, ma ha comunque le caratteristiche asintotiche desiderate).

Quindi, ovviamente, è necessario scansionare l'ingresso O(N), ma è possibile acquisire solo una piccola quantità di informazioni (definite in termini di k non N ) e quindi trovare in qualche modo i k numeri mancanti.


7
@polygenelubricants Grazie per i chiarimenti. "Sto cercando un algoritmo che utilizza il tempo O (N) e lo spazio O (K) in cui K è il conteggio dei numeri assenti" sarebbe stato chiaro dall'inizio ;-)
Dave O.

7
Dovresti precisare, nell'affermazione di Q1 che non puoi accedere ai numeri in ordine. Questo probabilmente ti sembra ovvio, ma non ho mai sentito parlare della domanda e il termine "bag" (che significa anche "multiset") è stato un po 'confuso.
Jérémie,

7
Si prega di leggere quanto segue in quanto le risposte fornite qui sono ridicole: stackoverflow.com/questions/4406110/…

18
La soluzione per sommare i numeri richiede lo spazio log (N) a meno che non si consideri O (1) il requisito di spazio per un numero intero senza limiti. Ma se permetti numeri interi non limitati, allora hai tutto lo spazio che vuoi con un solo numero intero.
Udo Klein,

3
A proposito, una soluzione alternativa piuttosto carina per Q1 potrebbe essere il calcolo XORdi tutti i numeri da 1a n, quindi risultati noiosi con tutti i numeri nella matrice data. Alla fine hai il tuo numero mancante. In questa soluzione non è necessario preoccuparsi dell'overflow come nel riepilogo.
sbeliakov,

Risposte:


590

Ecco un riassunto del link di Dimitris Andreou .

Ricorda la somma dell'i-esimo potere, dove i = 1,2, .., k. Ciò riduce il problema nella risoluzione del sistema di equazioni

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

Usando le identità di Newton , sapendo che i permette di calcolare

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

Se espandi il polinomio (xa 1 ) ... (xa k ) i coefficienti saranno esattamente c 1 , ..., c k - vedi le formule di Viète . Poiché ogni fattore polinomiale in modo univoco (l'anello di polinomi è un dominio euclideo ), ciò significa che un io è determinato in modo univoco, fino alla permutazione.

Questo termina una prova che ricordare i poteri è sufficiente per recuperare i numeri. Per k costante, questo è un buon approccio.

Tuttavia, quando k varia, l'approccio diretto del calcolo di c 1 , ..., c k è proibitivamente costoso, poiché ad esempio ck è il prodotto di tutti i numeri mancanti, magnitudine n! / (Nk) !. Per ovviare a questo, eseguire calcoli nel campo Z q , dove q è un numero primo tale che n <= q <2n - esiste dal postulato di Bertrand . Non è necessario modificare la dimostrazione, poiché le formule sono ancora valide e la fattorizzazione dei polinomi è ancora unica. È inoltre necessario un algoritmo per la fattorizzazione su campi finiti, ad esempio quello di Berlekamp o Cantor-Zassenhaus .

Pseudocodice di alto livello per costante k:

  • Calcola l'i-esimo potere di determinati numeri
  • Sottrai per ottenere somme di i-esimi poteri di numeri sconosciuti. Chiama le somme b i .
  • Utilizzare le identità di Newton per calcolare i coefficienti da b i ; chiamali c i . Fondamentalmente, c 1 = b 1 ; c 2 = (c 1 b 1 - b 2 ) / 2; vedi Wikipedia per le formule esatte
  • Fattore polinomiale x k -c 1 x k-1 + ... + c k .
  • Le radici del polinomio sono i numeri necessari a 1 , ..., a k .

Per variare k, trova un primo n <= q <2n usando ad esempio Miller-Rabin ed esegui i passaggi con tutti i numeri ridotti modulo q.

EDIT: La versione precedente di questa risposta affermava che invece di Z q , dove q è primo, è possibile usare un campo finito di caratteristica 2 (q = 2 ^ (log n)). Questo non è il caso, poiché le formule di Newton richiedono la divisione per numeri fino a k.


6
Non devi usare un campo primo, puoi anche usare q = 2^(log n). (Come hai realizzato i super- e gli abbonamenti ?!)
Heinrich Apfelmus,

49
+1 Questo è davvero molto intelligente. Allo stesso tempo, è discutibile se valga davvero la pena lo sforzo o se (parti di) questa soluzione a un problema abbastanza artificiale possa essere riutilizzata in un altro modo. E anche se questo fosse un problema del mondo reale, su molte piattaforme la O(N^2)soluzione più banale probabilmente supererà questa bellezza anche se ragionevolmente alta N. Mi fa pensare a questo: tinyurl.com/c8fwgw Tuttavia, ottimo lavoro! Non avrei avuto la pazienza di strisciare attraverso tutta la matematica :)
back2dos

167
Penso che questa sia una risposta meravigliosa. Penso che ciò illustri anche quanto sia scarso l'interrogatorio per estendere i numeri mancanti oltre uno. Anche il primo è una specie di gotchya, ma è abbastanza comune da mostrare "hai fatto un po 'di preparazione per l'intervista". Ma aspettarsi che un CS maggiore sappia andare oltre k = 1 (specialmente "sul posto" in un'intervista) è un po 'sciocco.
corsiKa

5
Questo sta effettivamente facendo la codifica di Reed Solomon sull'input.
David Ehrmann,

78
Scommetto che inserendo tutti i numeri in a hash sete iterando sulla 1...Nsuite usando le ricerche per determinare se mancano i numeri, sarebbe la soluzione più generica, la più veloce in media per quanto riguarda le kvariazioni, la soluzione più gestibile e comprensibile per il debug. Ovviamente la via della matematica è impressionante ma da qualche parte lungo la strada devi essere un ingegnere e non un matematico. Soprattutto quando si tratta di affari.
v.oddou,

243

Lo troverai leggendo un paio di pagine di Muthukrishnan - Algoritmi flusso di dati: Puzzle 1: Trovare numeri mancanti . Mostra esattamente la generalizzazione che stai cercando . Probabilmente questo è ciò che il tuo intervistatore ha letto e perché ha posto queste domande.

Ora, se solo le persone iniziassero a cancellare le risposte che sono state riassunte o sostituite dal trattamento di Muthukrishnan, e renderebbero più facile trovare questo testo. :)


Vedi anche la risposta direttamente correlata di sdcvvc , che include anche pseudocodice (evviva! Non c'è bisogno di leggere quelle complicate formulazioni matematiche :)) (grazie, ottimo lavoro!).


Oooh ... È interessante. Devo ammettere di essere stato un po 'confuso dalla matematica, ma lo stavo scremando. Potrebbe lasciarlo aperto per guardare più tardi. :) E +1 per ottenere questo link più reperibile. ;-)
Chris

2
Il link di Google Libri non funziona per me. Ecco una versione migliore [file PostScript].
Heinrich Apfelmus,

9
Wow. Non mi aspettavo che questo venisse votato! L'ultima volta che ho pubblicato un riferimento alla soluzione (Knuth's, in quel caso) invece di provare a risolverlo da solo, è stato effettivamente sottoposto a downgrade: stackoverflow.com/questions/3060104/… Il bibliotecario dentro di me si rallegra, grazie :)
Dimitris Andreou

@Apfelmus, nota che questa è una bozza. (Non ti biasimo, certo, ho confuso la bozza per le cose vere per quasi un anno prima di trovare il libro). Tuttavia, se il collegamento non ha funzionato, puoi andare su books.google.com e cercare "algoritmi del flusso di dati Muthukrishnan" (senza virgolette), è il primo a comparire.
Dimitris Andreou,

2
Si prega di leggere quanto segue in quanto le risposte fornite qui sono ridicole: stackoverflow.com/questions/4406110/…

174

Possiamo risolvere il Q2 sommando sia i numeri stessi sia i quadrati dei numeri.

Possiamo quindi ridurre il problema a

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

Dove x e in yche misura le somme sono inferiori ai valori previsti.

La sostituzione ci dà:

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

Che possiamo quindi risolvere per determinare i nostri numeri mancanti.


7
+1; Ho provato la formula in Maple per selezionare numeri e funziona. Tuttavia, non riesco ancora a convincermi PERCHÉ funziona.
poligenelubrificanti

4
@polygenelubricants: se si volesse dimostrare la correttezza, si dovrebbe innanzitutto dimostrare che fornisce sempre una soluzione corretta (ovvero, produce sempre una coppia di numeri che, rimuovendoli dall'insieme, comporterebbe il resto dell'insieme con la somma osservata e la somma dei quadrati). Da lì, dimostrare l'unicità è semplice come mostrare che produce solo una di queste coppie di numeri.
Anon.

5
La natura delle equazioni significa che otterrai quell'equazione due valori di k2. Tuttavia, dalla prima equazione che usi per generare k1, puoi vedere che questi due valori di k2 significheranno che k1 è l'altro valore, quindi hai due soluzioni che hanno gli stessi numeri al contrario. Se avessi dichiarato abitualmente che k1> k2, avresti solo una soluzione all'equazione quadratica e quindi una soluzione complessiva. E chiaramente per la natura della domanda esiste sempre una risposta, quindi funziona sempre.
Chris,

3
Per una data somma k1 + k2, ci sono molte coppie. Possiamo scrivere queste coppie come K1 = a + b e K2 = ab dove a = (K1 + k2 / 2). a è unico per una determinata somma. La somma dei quadrati (a + b) ** 2 + (ab) ** 2 = 2 * (a 2 + b 2). Per una data somma K1 + K2, il termine a 2 è fisso e vediamo che la somma dei quadrati sarà unica a causa del termine b 2. Pertanto, i valori xey sono univoci per una coppia di numeri interi.
phkahler,

8
Questo è bellissimo. @ user3281743 ecco un esempio. Lascia che i numeri mancanti (k1 e k2) siano 4 e 6. Somma (1 -> 10) = 55 e Somma (1 ^ 2 -> 10 ^ 2) = 385. Ora lascia che x = 55 - (Somma (Tutti i numeri rimanenti )) y = 385 - (Somma (quadrati di tutti i numeri rimanenti)) quindi x = 10 e y = 52. Sostituisci come mostrato che ci lascia con: (10 - k2) ^ 2 + k2 ^ 2 = 52 che puoi semplifica a: 2k ^ 2 - 20k + 48 = 0. Risolvere l'equazione quadratica ti dà 4 e 6 come risposta.
AlexKoren,

137

Come ha sottolineato @j_random_hacker, questo è abbastanza simile a Trovare i duplicati nello spazio O (n) e nello spazio O (1) , e qui funziona anche un adattamento della mia risposta.

Supponendo che la "borsa" sia rappresentata da una matrice A[]di dimensioni basata su 1 N - k, possiamo risolvere Qk nel O(N)tempo e O(k)nello spazio aggiuntivo.

Innanzitutto, estendiamo il nostro array A[]di kelementi, in modo che ora sia di dimensioni N. Questo è lo O(k)spazio aggiuntivo. Quindi eseguiamo il seguente algoritmo pseudo-codice:

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

Il primo ciclo inizializza le kvoci extra allo stesso della prima voce nella matrice (questo è solo un valore conveniente che sappiamo è già presente nella matrice - dopo questo passaggio, tutte le voci che mancavano nella matrice iniziale di dimensioni N-ksono manca ancora nell'array esteso).

Il secondo ciclo consente l'array esteso in modo che se l'elemento xè presente almeno una volta, una di quelle voci sarà in posizioneA[x] .

Si noti che sebbene abbia un ciclo nidificato, viene comunque eseguito O(N) tempo: uno swap si verifica solo se esiste itale A[i] != i, e ogni scambio imposta almeno un elemento tale A[i] == i, dove prima non era vero. Ciò significa che il numero totale di swap (e quindi il numero totale di esecuzioni del whilecorpo del loop) è al massimo N-1.

Il terzo ciclo stampa quegli indici dell'array iche non sono occupati dal valore i- questo significa chei devono essere stati mancanti.


4
Mi chiedo perché così poche persone votino questa risposta e nemmeno la abbiano contrassegnata come una risposta corretta. Ecco il codice in Python. Funziona in O (n) tempo e ha bisogno di spazio extra O (k). pastebin.com/9jZqnTzV
wall-e

3
@caf questo è abbastanza simile all'impostazione dei bit e al conteggio dei punti in cui il bit è 0. E penso che mentre stai creando un array intero sia occupata più memoria.
Fox,

5
"L'impostazione dei bit e il conteggio dei punti in cui il bit è 0" richiede O (n) spazio aggiuntivo, questa soluzione mostra come utilizzare O (k) spazio aggiuntivo.
Caf

7
Non funziona con gli stream come input e modifica l'array di input (anche se mi piace molto e l'idea è fruttuosa).
comco

3
@ v.oddou: No, va bene. Lo swap cambierà A[i], il che significa che la prossima iterazione non confronterà gli stessi due valori della precedente. Il nuovo A[i]sarà lo stesso dell'ultimo loop A[A[i]], ma il nuovo A[A[i]]sarà un nuovo valore. Provalo e vedi.
Caf

128

Ho chiesto a un bambino di 4 anni di risolvere questo problema. Ordinò i numeri e poi contò. Questo richiede uno spazio di O (piano cucina) e funziona altrettanto facilmente, anche se mancano molte palline.


20
;) il tuo bambino di 4 anni deve avvicinarsi al 5 o / ed è un genio. mia figlia di 4 anni non può nemmeno contare correttamente fino a 4. be ', per essere onesti, diciamo che alla fine ha appena integrato l' esistenza del "4". altrimenti fino ad ora lo avrebbe sempre saltato. "1,2,3,5,6,7" era la sua solita sequenza di conteggio. Le ho chiesto di aggiungere le matite insieme e avrebbe gestito 1 + 2 = 3 rinunciando di nuovo da zero. In realtà sono preoccupato ...: '(meh ..
v.oddou

approccio semplice ma efficace.
PabTorre,

6
O (piano cucina) ahah - ma non sarebbe O (n ^ 2)?

13
O (m²) immagino :)
Viktor Mellgren il

1
@phuclv: la risposta affermava che "Questo ha un requisito di spazio di O (piano cucina)". Ma in ogni caso, questo è un caso in cui l'ordinamento può essere raggiunto in O (n) tempo --- vedi questa discussione .
Anthony Labarre,

36

Non sono sicuro, se è la soluzione più efficiente, ma vorrei passare in rassegna tutte le voci e utilizzare un set di bit per ricordare quali numeri sono impostati, quindi testare 0 bit.

Mi piacciono le soluzioni semplici e credo persino che potrebbe essere più veloce del calcolo della somma o della somma dei quadrati ecc.


11
Ho proposto questa ovvia risposta, ma non è quello che l'intervistatore voleva. Ho detto esplicitamente nella domanda che questa non è la risposta che sto cercando. Un'altra ovvia risposta: ordina prima. Né l' O(N)ordinamento di conteggio né l'ordinamento di O(N log N)confronto sono ciò che sto cercando, sebbene siano entrambe soluzioni molto semplici.
poligenelubrificanti

@polygenelubricants: non riesco a trovare dove lo hai detto nella tua domanda. Se consideri il bitset come il risultato, allora non c'è un secondo passaggio. La complessità è (se consideriamo N come costante, come suggerisce l'intervistatore, dicendo che la complessità è "definita in k non N") O (1), e se devi costruire un risultato più "pulito", tu ottieni O (k), che è il massimo che puoi ottenere, perché hai sempre bisogno di O (k) per creare il risultato pulito.
Chris Lercher,

"Nota che non sto cercando l'ovvia soluzione basata su set (ad esempio usando un set di bit". Il secondo ultimo paragrafo della domanda originale.
hrnt

9
@hmt: Sì, la domanda è stata modificata pochi minuti fa. Sto solo dando la risposta, che mi aspetterei da un intervistato ... Costruire artificialmente una soluzione non ottimale (non puoi battere il tempo O (n) + O (k), non importa quello che fai) Non ha senso per me - tranne se non puoi permetterti O (n) spazio aggiuntivo, ma la domanda non è esplicita al riguardo.
Chris Lercher,

3
Ho modificato di nuovo la domanda per chiarire ulteriormente. Apprezzo il feedback / risposta.
poligenelubrificanti

33

Non ho controllato la matematica, ma ho il sospetto che il calcolo Σ(n^2)nello stesso passaggio in cui calcoliamo Σ(n)fornirebbe abbastanza informazioni per ottenere due numeri mancanti, fare Σ(n^3)pure se ce ne sono tre e così via.


15

Il problema con le soluzioni basate su somme di numeri è che non tengono conto del costo di archiviazione e utilizzo di numeri con esponenti di grandi dimensioni ... in pratica, perché funzionasse per n molto grandi, verrebbe utilizzata una libreria di grandi numeri . Possiamo analizzare l'utilizzo dello spazio per questi algoritmi.

Siamo in grado di analizzare la complessità temporale e spaziale degli algoritmi di sdcvvc e Dimitris Andreou.

Conservazione:

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

Così l_j \in \Theta(j log n)

Spazio di archiviazione totale utilizzato: \sum_{j=1}^k l_j \in \Theta(k^2 log n)

Spazio utilizzato: supponendo che l'elaborazione a^jrichieda ceil(log_2 j)tempo, tempo totale:

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)

Tempo totale utilizzato: \Theta(kn log n)

Se questo tempo e spazio sono soddisfacenti, è possibile utilizzare un semplice algoritmo ricorsivo. Lascia che b! Io sia la voce nella borsa, n il numero di numeri prima delle rimozioni e k il numero di rimozioni. Nella sintassi di Haskell ...

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

Spazio di archiviazione utilizzato: O(k)per elenco, O(log(n))per stack: O(k + log(n)) questo algoritmo è più intuitivo, ha la stessa complessità temporale e utilizza meno spazio.


1
+1, sembra carino ma mi hai perso passando dalla riga 4 alla riga 5 nello snippet n. 1 - potresti spiegarlo ulteriormente? Grazie!
j_random_hacker,

isInRangeè O (log n) , non O (1) : confronta i numeri nell'intervallo 1..n, quindi deve confrontare i bit O (log n) . Non so fino a che punto questo errore influenzi il resto dell'analisi.
jcsahnwaldt dice GoFundMonica il

14

Apetta un minuto. Come indicato nella domanda, ci sono 100 numeri nella borsa. Non importa quanto sia grande k, il problema può essere risolto in tempo costante perché è possibile utilizzare un set e rimuovere i numeri dal set in al massimo 100 iterazioni di un ciclo. 100 è costante. L'insieme dei numeri rimanenti è la tua risposta.

Se generalizziamo la soluzione ai numeri da 1 a N, nulla cambia tranne N non è una costante, quindi siamo nel tempo O (N - k) = O (N). Ad esempio, se utilizziamo un set di bit, impostiamo i bit su 1 nel tempo O (N), ripetiamo i numeri, impostando i bit su 0 mentre procediamo (O (Nk) = O (N)) e quindi avere la risposta.

Mi sembra che l'intervistatore ti stesse chiedendo come stampare il contenuto del set finale in O (k) time anziché O (N) time. Chiaramente, con un bit impostato, è necessario scorrere tutti gli N bit per determinare se è necessario stampare il numero o meno. Tuttavia, se si modifica la modalità di implementazione del set, è possibile stampare i numeri in k iterazioni. Questo viene fatto inserendo i numeri in un oggetto da archiviare sia in un set di hash che in un elenco doppiamente collegato. Quando rimuovi un oggetto dal set di hash, lo rimuovi anche dall'elenco. Le risposte verranno lasciate nell'elenco che ora è di lunghezza k.


9
Questa risposta è troppo semplice e sappiamo tutti che le risposte semplici non funzionano! ;) Scherzi a parte, la domanda originale dovrebbe probabilmente enfatizzare i requisiti di spazio O (k).
DK.

Il problema non è semplice ma è necessario utilizzare O (n) memoria aggiuntiva per la mappa. Il problema mi ha risolto in tempo costante e memoria costante
Mojo Risin,

3
Scommetto che puoi dimostrare che la soluzione minima è almeno O (N). perché meno, significherebbe che non hai nemmeno guardato alcuni numeri e poiché non è stato specificato alcun ordine, è obbligatorio guardare TUTTI i numeri.
v.oddou,

Se consideriamo l'input come un flusso e n è troppo grande per essere tenuto in memoria, il requisito di memoria O (k) ha senso. Possiamo comunque usare l'hash: basta creare k ^ 2 bucket e usare il semplice algoritmo di somma su ciascuno di essi. Questa è solo la memoria di k ^ 2 e alcuni altri bucket possono essere utilizzati per ottenere un'alta probabilità di successo.
Thomas Ahle,

8

Per risolvere la domanda 2 (e 3) sui numeri mancanti, è possibile modificare quickselect, che in media viene eseguito O(n)e utilizza la memoria costante se il partizionamento viene eseguito sul posto.

  1. Partizionare l'insieme rispetto a un perno casuale pin partizioni l, che contengono numeri più piccoli del perno e rche contengono numeri maggiori del perno.

  2. Determina in quali partizioni si trovano i 2 numeri mancanti confrontando il valore pivot con la dimensione di ciascuna partizione ( p - 1 - count(l) = count of missing numbers in le n - count(r) - p = count of missing numbers in r)

  3. a) Se in ogni partizione manca un numero, utilizzare l'approccio differenza di somme per trovare ciascun numero mancante.

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

    b) Se in una partizione mancano entrambi i numeri e la partizione è vuota, i numeri mancanti sono (p-1,p-2)o(p+1,p+2) seconda di quale partizione mancano i numeri.

    Se in una partizione mancano 2 numeri ma non è vuota, ricorrere a quel partitone.

Con solo 2 numeri mancanti, questo algoritmo scarta sempre almeno una partizione, quindi mantiene O(n) complessità temporale media della selezione rapida. Allo stesso modo, con 3 numeri mancanti questo algoritmo scarta anche almeno una partizione con ogni passaggio (perché come con 2 numeri mancanti, al massimo solo 1 partizione conterrà più numeri mancanti). Tuttavia, non sono sicuro di quanto le prestazioni diminuiscono quando vengono aggiunti più numeri mancanti.

Ecco un'implementazione che non utilizza il partizionamento sul posto, quindi questo esempio non soddisfa i requisiti di spazio ma illustra i passaggi dell'algoritmo:

<?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);
    }
  }

dimostrazione


Partizionare il set è come usare lo spazio lineare. Almeno non funzionerebbe in un'impostazione di streaming.
Thomas Ahle,

@ThomasAhle see en.wikipedia.org/wiki/Selection_algorithm#Space_complexity . la suddivisione in serie dell'insieme richiede solo O (1) spazio aggiuntivo, non spazio lineare. In un'impostazione di streaming sarebbe O (k) spazio aggiuntivo, tuttavia, la domanda originale non menziona lo streaming.
FuzzyTree,

Non direttamente, ma scrive "devi scansionare l'input in O (N), ma puoi catturare solo una piccola quantità di informazioni (definita in termini di k non N)" che di solito è la definizione di streaming. Spostare tutti i numeri per il partizionamento non è davvero possibile a meno che tu non abbia una matrice di dimensioni N. È solo che la domanda ha molte risposte che sembrano ignorare questo vincolo.
Thomas Ahle,

1
Ma come dici tu, le prestazioni potrebbero diminuire con l'aggiunta di più numeri? Possiamo anche usare l'algoritmo mediano del tempo lineare, per ottenere sempre un taglio perfetto, ma se i numeri k sono ben distribuiti in 1, ..., n, non dovrai andare su livelli di logk "profondi" prima di poter potare qualche ramo?
Thomas Ahle,

2
Il tempo di esecuzione nel caso peggiore è effettivamente nlogk perché è necessario elaborare l'intero input al massimo dei tempi di logk, quindi è una sequenza geometrica (quella che inizia con al massimo n elementi). I requisiti di spazio vengono registrati quando implementati con semplice ricorsione, ma possono essere resi O (1) eseguendo una selezione rapida effettiva e garantendo la lunghezza corretta di ciascuna partizione.
emu

7

Ecco una soluzione che utilizza k bit di spazio di archiviazione aggiuntivo, senza trucchi intelligenti e semplici. Tempo di esecuzione O (n), spazio extra O (k). Solo per dimostrare che questo può essere risolto senza prima leggere la soluzione o essere un genio:

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; }
    }
}

Lo volevi (data [n - 1 - odd] % 2 == 1) ++odd;?
Charles,

2
Potresti spiegare come funziona? Non capisco.
Teepeemm,

La soluzione sarebbe molto, molto semplice se potessi usare un array di (n + k) booleani per l'archiviazione temporanea, ma ciò non è consentito. Quindi riorganizzo i dati, mettendo i numeri pari all'inizio e i numeri dispari alla fine dell'array. Ora i bit più bassi di questi n numeri possono essere utilizzati per la memorizzazione temporanea, perché so quanti numeri pari e dispari ci sono e posso ricostruire i bit più bassi! Questi n bit e i k bit extra sono esattamente i booleani (n + k) di cui avevo bisogno.
gnasher729,

2
Questo non funzionerebbe se i dati fossero troppo grandi per essere conservati in memoria e tu li vedessi solo come un flusso. Deliciously hacky però :)
Thomas Ahle,

La complessità dello spazio può essere O (1). In un primo passaggio, elabori tutti i numeri <(n - k) esattamente con questo algoritmo, senza usare "extra". In un secondo passaggio, si cancellano nuovamente i bit di parità e si utilizzano le prime k posizioni per indicizzare i numeri (nk) .. (n).
emu,

5

Puoi controllare se esiste un numero qualsiasi? Se sì, puoi provare questo:

S = somma di tutti i numeri nel sacchetto (S <5050)
Z = somma dei numeri mancanti 5050 - S

se i numeri mancanti sono xe yquindi:

x = Z - ye
max (x) = Z - 1

Quindi controlli l'intervallo da 1a max(x)e trovi il numero


1
Cosa max(x)significa quando xè un numero?
Thomas Ahle,

2
probabilmente significa max dall'insieme di numeri
JavaHopper

se avessimo più di 2 numeri questa soluzione verrebbe eliminata
ozgeneral

4

Può essere questo algoritmo può funzionare per la domanda 1:

  1. Calcola xor dei primi 100 numeri interi (val = 1 ^ 2 ^ 3 ^ 4 .... 100)
  2. xo gli elementi mentre continuano a provenire dal flusso di input (val1 = val1 ^ next_input)
  3. risposta finale = val ^ val1

O ancora meglio:

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

Questo algoritmo può infatti essere espanso per due numeri mancanti. Il primo passo rimane lo stesso. Quando chiamiamo GetValue con due numeri mancanti, il risultato sarà a a1^a2sono i due numeri mancanti. Diciamo

val = a1^a2

Ora per setacciare a1 e a2 da val prendiamo qualsiasi bit impostato in val. Diciamo che il ithbit è impostato in val. Ciò significa che a1 e a2 hanno parità diverse nella ithposizione dei bit. Ora eseguiamo un'altra iterazione sull'array originale e manteniamo due valori xor. Uno per i numeri che hanno l'ith bit impostato e l'altro che non ha l'ith bit impostato. Ora abbiamo due secchi di numeri, ed è garantito che si a1 and a2troverà in diversi secchi. Ora ripeti lo stesso di quello che abbiamo fatto per trovare un elemento mancante su ciascun bucket.


Questo risolve solo il problema k=1, giusto? Ma mi piace usare xorpiù somme, sembra un po 'più veloce.
Thomas Ahle,

@ThomasAhle Sì. L'ho chiamato nella mia risposta.
bashrc,

Giusto. Hai idea di quale potrebbe essere un xor del "secondo ordine", per k = 2? Simile all'utilizzo dei quadrati per la somma, possiamo "quadrare" per xor?
Thomas Ahle,

1
@ThomasAhle È stato modificato per funzionare con 2 numeri mancanti.
bashrc,

questo è il mio modo preferito :)
Robert King

3

Puoi risolvere il Q2 se hai la somma di entrambi gli elenchi e il prodotto di entrambi gli elenchi.

(l1 è l'originale, l2 è l'elenco modificato)

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

Possiamo ottimizzarlo poiché la somma di una serie aritmetica è n volte la media del primo e dell'ultimo termine:

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

Ora sappiamo che (se aeb sono i numeri rimossi):

a + b = d
a * b = m

Quindi possiamo riorganizzare in:

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

E moltiplicare:

-b^2 + s*b = m

E riorganizzare in modo che il lato destro sia zero:

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

Quindi possiamo risolvere con la formula quadratica:

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

Codice di esempio Python 3:

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

Non conosco la complessità delle funzioni sqrt, riduci e somma, quindi non riesco a capire la complessità di questa soluzione (se qualcuno lo sa, commenta di seguito).


Quanto tempo e memoria utilizza per calcolare x1*x2*x3*...?
Thomas Ahle,

@ThomasAhle È O (n) -time e O (1) -spazio sulla lunghezza della lista, ma in realtà è più come moltiplicazione (almeno in Python) è O (n ^ 1.6) -time sulla lunghezza di il numero e i numeri sono O (log n) -space sulla loro lunghezza.
Tuomas Laakkonen,

@ThomasAhle No, log (a ^ n) = n * log (a) in modo da avere O (l log k) -space per memorizzare il numero. Quindi, dato un elenco di lunghezza 1 e numeri originali di lunghezza k, avresti spazio O (l) ma il fattore costante (log k) sarebbe inferiore rispetto alla semplice scrittura di tutti. (Non credo che il mio metodo sia un modo particolarmente efficace per rispondere alla domanda.)
Tuomas Laakkonen,

3

Per Q2 questa è una soluzione un po 'più inefficiente rispetto alle altre, ma ha ancora runtime O (N) e occupa spazio O (k).

L'idea è di eseguire l'algoritmo originale due volte. Nel primo ottieni un numero totale mancante, che ti dà un limite superiore dei numeri mancanti. Chiamiamo questo numero N. Sai che i due numeri mancanti si sommeranno N, quindi il primo numero può essere solo nell'intervallo [1, floor((N-1)/2)]mentre il secondo sarà in[floor(N/2)+1,N-1] .

Pertanto, esegui nuovamente il ciclo su tutti i numeri, scartando tutti i numeri che non sono inclusi nel primo intervallo. Quelli che sono, tieni traccia della loro somma. Infine, conoscerai uno dei due numeri mancanti e, per estensione, il secondo.

Ho la sensazione che questo metodo possa essere generalizzato e che forse più ricerche vengano eseguite in "parallelo" durante un singolo passaggio sull'input, ma non ho ancora capito come.


Ah, sì, questa è la stessa soluzione che mi è venuta in mente per il secondo trimestre, solo per calcolare nuovamente la somma prendendo i negativi per tutti i numeri inferiori a N / 2, ma è ancora meglio!
xjcl

2

Penso che ciò possa essere fatto senza equazioni e teorie matematiche complesse. Di seguito è una proposta per una soluzione di complessità temporale sul posto e O (2n):

Presupposti per i moduli di input:

Numero di numeri in borsa = n

# di numeri mancanti = k

I numeri nella busta sono rappresentati da una matrice di lunghezza n

Lunghezza dell'array di input per l'algo = n

Le voci mancanti nell'array (numeri estratti dal sacchetto) vengono sostituite dal valore del primo elemento dell'array.

Per esempio. Inizialmente la borsa sembra [2,9,3,7,8,6,4,5,1,10]. Se viene rimosso 4, il valore di 4 diventerà 2 (il primo elemento dell'array). Pertanto, dopo aver estratto 4 il sacchetto apparirà come [2,9,3,7,8,6,2,5,1,10]

La chiave di questa soluzione è contrassegnare l'INDICE di un numero visitato negando il valore in quell'INDICE mentre l'array viene attraversato.

    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;
    }

Questo utilizza troppa memoria.
Thomas Ahle,

2

Esiste un modo generale per generalizzare algoritmi di streaming come questo. L'idea è di usare un po 'di randomizzazione per sperare di "spargere" gli kelementi in sotto-problemi indipendenti, dove il nostro algoritmo originale risolve il problema per noi. Questa tecnica viene utilizzata, tra le altre cose, nella ricostruzione dei segnali sparsi.

  • Crea un array a, di dimensioni u = k^2.
  • Scegliere qualsiasi funzione hash universale , h : {1,...,n} -> {1,...,u}. (Come moltiplicare il turno )
  • Per ciascuno iin 1, ..., naumentoa[h(i)] += i
  • Per ogni numero xnel flusso di input, decrementa a[h(x)] -= x.

Se tutti i numeri mancanti sono stati sottoposti a hash su diversi bucket, gli elementi diversi da zero dell'array conterranno ora i numeri mancanti.

La probabilità che una determinata coppia venga inviata allo stesso bucket, è inferiore rispetto 1/ualla definizione di una funzione hash universale. Dato che ci sono circa k^2/2coppie, abbiamo che la probabilità di errore è al massimo k^2/2/u=1/2. Cioè, riusciamo con probabilità almeno al 50% e se aumentiamo uaumentiamo le nostre possibilità.

Si noti che questo algoritmo richiede k^2 lognbit di spazio (abbiamo bisogno di lognbit per bucket di array.) Corrisponde allo spazio richiesto dalla risposta di @Dimitris Andreou (In particolare il requisito di spazio della fattorizzazione polinomiale, che risulta essere anche randomizzato). Anche questo algoritmo ha costante tempo per aggiornamento, piuttosto che tempo knel caso di somme di energia.

In effetti, possiamo essere persino più efficienti del metodo della somma di potenza usando il trucco descritto nei commenti.


Nota: possiamo anche usare xorin ogni secchio, piuttosto che sum, se è più veloce sulla nostra macchina.
Thomas Ahle,

Interessante ma penso che questo rispetti il ​​vincolo di spazio solo quando k <= sqrt(n)- almeno se u=k^2? Supponiamo che k = 11 e n = 100, allora avresti 121 bucket e l'algoritmo finirà per essere simile ad avere un array di 100 bit che controlli mentre leggi ogni # dallo stream. L'aumento umigliora le possibilità di successo, ma esiste un limite a quanto è possibile aumentarlo prima di superare il vincolo di spazio.
FuzzyTree

1
Il problema ha molto più senso di nmolto più di k, credo, ma puoi effettivamente ottenere spazio k logncon un metodo molto simile all'hash descritto, pur mantenendo aggiornamenti di tempo costanti. È descritto in gnunet.org/eppstein-set-reconciliation , come il metodo della somma dei poteri, ma fondamentalmente hai hash su 'due di k' bucket con una forte funzione hash come hash tabulazione, che garantisce che alcuni bucket avranno solo un elemento . Per decodificare, identifichi quel bucket e rimuovi l'elemento da entrambi i bucket, il che (probabilmente) libera un altro bucket e così via
Thomas Ahle,

2

Una soluzione molto semplice a Q2 che mi sorprende che nessuno abbia già risposto. Utilizzare il metodo da Q1 per trovare la somma dei due numeri mancanti. Indichiamolo con S, quindi uno dei numeri mancanti è più piccolo di S / 2 e l'altro è più grande di S / 2 (duh). Somma tutti i numeri da 1 a S / 2 e confrontali con il risultato della formula (in modo simile al metodo in Q1) per trovare il numero più basso tra i numeri mancanti. Sottrai da S per trovare il numero mancante più grande.


Penso che sia la stessa della risposta di Svalorzen , ma l'hai spiegato in parole migliori. Hai idea di come generalizzarlo a Qk?
John McClane,

Ci scusiamo per aver perso l'altra risposta. Non sono sicuro che sia possibile generalizzarlo a $ Q_k $ poiché in quel caso non è possibile associare l'elemento mancante più piccolo a un intervallo. Sai che alcuni elementi devono essere inferiori a $ S / k $ ma ciò potrebbe essere vero per più elementi
Gilad Deutsch,

1

Problema molto bello. Vorrei usare una differenza prestabilita per Qk. Molti linguaggi di programmazione ne hanno persino il supporto, come in Ruby:

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

Probabilmente non è la soluzione più efficiente, ma è quella che userei nella vita reale se dovessi affrontare un simile compito in questo caso (confini noti, confini bassi). Se l'insieme di numeri fosse molto grande, prenderei in considerazione un algoritmo più efficiente, ovviamente, ma fino ad allora la soluzione semplice sarebbe stata sufficiente per me.


1
Questo utilizza troppo spazio.
Thomas Ahle,

@ThomasAhle: Perché aggiungi commenti inutili ad ogni seconda risposta? Che cosa vuoi dire con che sta usando troppo spazio?
DarkDust,

Perché la domanda dice che "Non possiamo permetterci uno spazio aggiuntivo proporzionale a N." Questa soluzione fa esattamente questo.
Thomas Ahle,

1

Puoi provare a usare un filtro Bloom . Inserisci ogni numero nella busta nella fioritura, quindi esegui l'iterazione sul set completo da 1 k fino a segnalare quelli non trovati. Questo potrebbe non trovare la risposta in tutti gli scenari, ma potrebbe essere una soluzione abbastanza buona.


C'è anche il filtro di conteggio delle fioriture, che consente la cancellazione. Quindi puoi semplicemente aggiungere tutti i numeri ed eliminare quelli che vedi nello stream.
Thomas Ahle,

Haha, questa è probabilmente una delle risposte più pratiche, ma riceve poca attenzione.
Cane

1

Adotterò un approccio diverso a quella domanda e sonderei l'intervistatore per maggiori dettagli sul problema più grande che sta cercando di risolvere. A seconda del problema e dei requisiti che lo circondano, l'ovvia soluzione basata su set potrebbe essere la cosa giusta e l'approccio generare-un-elenco-e-pick-through-it-after potrebbe non esserlo.

Ad esempio, potrebbe essere che l'intervistatore invii nmessaggi e debba conoscere il . Successivamente, il set contiene l'elenco di elementi mancanti e non è necessario eseguire ulteriori elaborazioni.k che ciò non ha prodotto una risposta e che deve conoscerlo nel minor tempo possibile dopo iln-k arriva la risposta. Diciamo anche che la natura del canale dei messaggi è tale che, anche a pieno ritmo, c'è abbastanza tempo per fare un po 'di elaborazione tra i messaggi senza avere alcun impatto sul tempo impiegato per produrre il risultato finale dopo che arriva l'ultima risposta. Quel tempo può essere utilizzato inserendo un aspetto identificativo di ciascun messaggio inviato in un set ed eliminandolo quando arriva ogni risposta corrispondente. Una volta arrivata l'ultima risposta, l'unica cosa da fare è rimuovere il suo identificatore dal set, che nelle implementazioni tipiche richiedeO(log k+1)k

Questo non è certamente l'approccio più veloce per l'elaborazione batch di numeri preconfezionati perché tutto funziona O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k)). Ma funziona per qualsiasi valore di k(anche se non è noto in anticipo) e nell'esempio sopra è stato applicato in modo da ridurre al minimo l'intervallo più critico.


Funzionerebbe se avessi solo O (k ^ 2) memoria aggiuntiva?
Thomas Ahle,

1

Puoi motivare la soluzione pensandoci in termini di simmetrie (gruppi, in linguaggio matematico). Indipendentemente dall'ordine dell'insieme di numeri, la risposta dovrebbe essere la stessa. Se utilizzerai le kfunzioni per determinare gli elementi mancanti, dovresti pensare a quali funzioni hanno quella proprietà: simmetrica. La funzione s_1(x) = x_1 + x_2 + ... + x_nè un esempio di funzione simmetrica, ma ce ne sono altre di grado superiore. In particolare, considerare le funzioni simmetriche elementari . La funzione simmetrica elementare del grado 2 è s_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_nla somma di tutti i prodotti di due elementi. Allo stesso modo per le elementari funzioni simmetriche di grado 3 e superiore. Sono ovviamente simmetrici. Inoltre, risulta che sono i mattoni per tutte le funzioni simmetriche.

Puoi costruire le funzioni simmetriche elementari man mano che procedi osservando che s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1)). Ulteriori riflessioni dovrebbero convincerti a farlo s_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))e così via, in modo che possano essere calcolate in un unico passaggio.

Come facciamo a sapere quali elementi mancavano dall'array? Pensa al polinomio (z-x_1)(z-x_2)...(z-x_n). Valuta 0se si inserisce uno dei numeri x_i. Espandendo il polinomio, ottieni z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n. Le funzioni simmetriche elementari appaiono anche qui, il che non sorprende, poiché il polinomio dovrebbe rimanere lo stesso se applichiamo una permutazione alle radici.

Quindi possiamo costruire il polinomio e provare a fattorizzarlo per capire quali numeri non sono nell'insieme, come altri hanno già detto.

Infine, se siamo preoccupati di traboccare di memoria con grandi numeri (l'ennesimo polinomio simmetrico sarà dell'ordine 100!), possiamo fare questi calcoli in mod pcui pun numero primo è maggiore di 100. In quel caso valutiamo il polinomio mod pe scopriamo che valuta di nuovo a 0quando l'ingresso è un numero nell'insieme e valuta un valore diverso da zero quando l'ingresso è un numero non nell'insieme. Tuttavia, come altri hanno sottolineato, per ottenere i valori dal polinomio nel tempo che dipende k, non Ndobbiamo considerare il polinomio mod p.


1

Ancora un altro modo è utilizzare il filtro grafico residuo.

Supponiamo che manchino i numeri da 1 a 4 e 3. La rappresentazione binaria è la seguente,

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

E posso creare un diagramma di flusso come il seguente.

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

Si noti che il diagramma di flusso contiene x nodi, mentre x è il numero di bit. E il numero massimo di spigoli è (2 * x) -2.

Quindi per i numeri interi a 32 bit ci vorrà O (32) spazio o O (1) spazio.

Ora se rimuovo la capacità di ciascun numero a partire da 1,2,4, allora rimango con un grafico residuo.

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

Alla fine eseguirò un ciclo come il seguente,

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

Ora il risultato è in resultcontiene anche numeri che non mancano (falso positivo). Ma k <= (dimensione del risultato) <= n quando ci sono kelementi mancanti.

Esaminerò la lista data un'ultima volta per contrassegnare il risultato mancante o meno.

Quindi la complessità temporale sarà O (n).

Infine, è possibile ridurre il numero di falsi positivi (e lo spazio richiesto) prendendo nodi 00, 01, 11, 10invece di 0e 1.


Non capisco il tuo diagramma grafico. Cosa rappresentano i nodi, i bordi e i numeri? Perché alcuni dei bordi sono diretti e non altri?
dain

In realtà non capisco davvero la tua risposta, puoi chiarire un po 'di più?
dain

1

Probabilmente avresti bisogno di chiarimenti sul significato di O (k).

Ecco una soluzione banale per k arbitraria: per ogni v nel tuo set di numeri, accumula la somma di 2 ^ v. Alla fine, ciclo i da 1 a N. Se la somma AND bit a bit con 2 ^ i è zero, allora i manca. (O numericamente, se il piano della somma diviso per 2 ^ i è pari. Or sum modulo 2^(i+1)) < 2^i.)

Facile vero? O (N) time, O (1) storage, e supporta arbitrario k.

Tranne il fatto che stai calcolando numeri enormi che su un computer reale richiederebbero ciascuno spazio O (N). In effetti, questa soluzione è identica a un vettore bit.

Quindi potresti essere intelligente e calcolare la somma e la somma dei quadrati e la somma dei cubi ... fino alla somma di v ^ k, e fare la matematica fantasia per estrarre il risultato. Ma anche quelli sono grandi numeri, il che pone la domanda: di quale modello astratto di operazione stiamo parlando? Quanto si adatta allo spazio O (1) e quanto tempo ci vuole per riassumere i numeri di qualsiasi dimensione tu abbia bisogno?


Bella risposta! Una piccola cosa: "Se la somma modulo 2 ^ i è zero, allora mi manca" non è corretta. Ma è chiaro cosa si intende. Penso che "se la somma modulo 2 ^ (i + 1) è inferiore a 2 ^ i, allora mi manca" sarebbe corretta. (Certo, nella maggior parte dei linguaggi di programmazione
useremmo il

1
Grazie, hai perfettamente ragione! Risolto, anche se ero pigro e smarrito dalla notazione matematica ... oh, e l'ho anche incasinato.
Risolto il problema

1

Ecco una soluzione che non si basa su una matematica complessa come fanno le risposte di sdcvvc / Dimitris Andreou, non cambia l'array di input come hanno fatto caf e il colonnello Panic e non usa un bit di dimensioni enormi come Chris Lercher, JeremyP e molti altri hanno fatto. Fondamentalmente, ho iniziato con l'idea di Svalorzen / Gilad Deutch per Q2, l'ho generalizzato al caso comune Qk e implementato in Java per dimostrare che l'algoritmo funziona.

L'idea

Supponiamo di avere un intervallo arbitrario I di cui sappiamo solo che contiene almeno uno dei numeri mancanti. Dopo un passaggio attraverso l'array di input, guardando solo i numeri da Io , possiamo ottenere sia la somma S e la quantità Q di numeri mancanti da I . Lo facciamo semplicemente diminuendo la lunghezza di I ogni volta che incontriamo un numero da I (per ottenere Q ) e diminuendo la somma precalcolata di tutti i numeri in I di quel numero rilevato ogni volta (per ottenere S ).

Ora guardiamo S e Q . Se Q = 1 , significa che allora mi compare soltanto uno dei numeri mancanti, e questo numero è chiaramente S . Segniamo I come finito (nel programma è chiamato "non ambiguo") e lo tralasciamo da ulteriori considerazioni. D'altra parte, se Q> 1 , siamo in grado di calcolare la media A = S / Q dei numeri mancanti contenuto in I . Poiché tutti i numeri sono distinti, almeno uno di questi numeri è strettamente minore di A e almeno uno è strettamente maggiore di A . Ora abbiamo diviso I in Ain due intervalli più piccoli ciascuno dei quali contiene almeno un numero mancante. Nota che non importa a quale degli intervalli assegniamo A nel caso in cui sia un numero intero.

Effettuiamo il passaggio dell'array successivo calcolando S e Q per ciascuno degli intervalli separatamente (ma nello stesso passaggio) e successivamente contrassegniamo gli intervalli con Q = 1 e dividiamo gli intervalli con Q> 1 . Continuiamo questo processo fino a quando non ci sono nuovi intervalli "ambigui", cioè non abbiamo nulla da dividere perché ogni intervallo contiene esattamente un numero mancante (e conosciamo sempre questo numero perché conosciamo S ). Partiamo dall'unico intervallo "intero intervallo" contenente tutti i possibili numeri (come [1..N] nella domanda).

Analisi della complessità del tempo e dello spazio

Il numero totale di passaggi p che dobbiamo effettuare fino a quando il processo si arresta non è mai maggiore del conteggio dei numeri mancanti k . La disuguaglianza p <= k può essere dimostrata rigorosamente. D'altra parte, esiste anche un limite superiore empirico p <log 2 N + 3 che è utile per grandi valori di k . È necessario effettuare una ricerca binaria per ciascun numero dell'array di input per determinare l'intervallo a cui appartiene. Ciò aggiunge il moltiplicatore log k alla complessità temporale.

In totale, la complessità temporale è O (N ᛫ min (k, log N) ᛫ log k) . Nota che per k di grandi dimensioni , questo è significativamente migliore rispetto a quello del metodo sdcvvc / Dimitris Andreou, che è O (N ᛫ k) .

Per il suo lavoro, l'algoritmo richiede O (k) spazio aggiuntivo per la memorizzazione alla maggior parte degli intervalli di k , che è significativamente migliore di O (N) nelle soluzioni "bitset".

Implementazione Java

Ecco una classe Java che implementa l'algoritmo sopra. Restituisce sempre una matrice ordinata di numeri mancanti. Oltre a ciò, non richiede che i numeri mancanti contino k perché lo calcola al primo passaggio. L'intero intervallo di numeri è dato dai parametri minNumbere maxNumber(es. 1 e 100 per il primo esempio nella domanda).

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;
    }
}

Per onestà, questa classe riceve input sotto forma di NumberBagoggetti. NumberBagnon consente la modifica dell'array e l'accesso casuale e conta anche quante volte l'array è stato richiesto per l'attraversamento sequenziale. È anche più adatto per test di array di grandi dimensioni che Iterable<Integer>perché evita l'inscatolamento di intvalori primitivi e consente di avvolgere una parte di un grande int[]per una comoda preparazione del test. Non è difficile sostituire, se lo si desidera, NumberBagcon int[]o Iterable<Integer>digitare la findfirma, cambiando in essa due for-loop in foreach.

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();
            }
        };
    }
}

test

Di seguito sono riportati semplici esempi che dimostrano l'utilizzo di queste classi.

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");
    }
}

In questo modo è possibile eseguire test di array di grandi dimensioni:

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

Provali su Ideone


0

Credo di avere un algoritmo di O(k)tempo e O(log(k))spazio, dato che hai a disposizione le funzioni floor(x)e log2(x)per numeri interi arbitrariamente grandi:

Hai un kintero lungo a bit (quindi lo log8(k)spazio) in cui aggiungi il punto x^2, dove x è il numero successivo che trovi nel sacchetto: s=1^2+2^2+...questo richiede O(N)tempo (che non è un problema per l'intervistatore). Alla fine ottieni j=floor(log2(s))qual è il numero più grande che stai cercando. Quindi s=s-jfai di nuovo quanto sopra:

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

Ora, di solito non hai le funzioni floor e log2 per gli 2756interi -bit ma invece per i doppi. Così? Semplicemente, per ogni 2 byte (o 1, o 3 o 4) è possibile utilizzare queste funzioni per ottenere i numeri desiderati, ma ciò aggiunge un O(N)fattore alla complessità temporale


0

Questo potrebbe sembrare stupido, ma, nel primo problema presentato, dovresti vedere tutti i numeri rimanenti nella borsa per sommarli effettivamente per trovare il numero mancante usando quell'equazione.

Quindi, dato che riesci a vedere tutti i numeri, cerca solo il numero che manca. Lo stesso vale per la mancanza di due numeri. Abbastanza semplice, penso. Non ha senso usare un'equazione quando riesci a vedere i numeri rimanenti nella borsa.


2
Penso che il vantaggio di riassumerli sia che non devi ricordare quali numeri hai già visto (ad esempio, non c'è bisogno di memoria aggiuntiva). Altrimenti l'unica opzione è quella di conservare un set di tutti i valori visti e quindi scorrere nuovamente su quel set per trovare quello che manca.
Dan Tao,

3
Questa domanda viene generalmente posta con la stipula della complessità dello spazio O (1).

La somma dei primi N numeri è N (N + 1) / 2. Per N = 100, Somma = 100 * (101) / 2 = 5050;
tmarthal,

0

Penso che questo possa essere generalizzato in questo modo:

Indicare S, M come valori iniziali per la somma delle serie aritmetiche e della moltiplicazione.

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

Dovrei pensare a una formula per calcolare questo, ma non è questo il punto. Comunque, se manca un numero, hai già fornito la soluzione. Tuttavia, se mancano due numeri, denotiamo la nuova somma e il multiplo totale per S1 e M1, che saranno i seguenti:

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

Where a and b are the missing numbers.

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

Poiché conosci S1, M1, M e S, l'equazione sopra è risolvibile per trovare aeb, i numeri mancanti.

Ora per i tre numeri mancanti:

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

Where a and b are the missing numbers.

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

Ora il tuo sconosciuto è 3 mentre hai solo due equazioni che puoi risolvere.


La moltiplicazione diventa piuttosto grande però ... Inoltre, come si fa a generalizzare a più di 2 numeri mancanti?
Thomas Ahle,

Ho provato queste formule in una sequenza molto semplice con N = 3 e numeri mancanti = {1, 2}. Non ho funzionato, poiché credo che l'errore sia nelle formule (2) che dovrebbero leggere M1 = M / (a * b)(vedi quella risposta ). Quindi funziona benissimo.
dma_k,

0

Non so se sia efficace o meno, ma vorrei suggerire questa soluzione.

  1. Calcola xor dei 100 elementi
  2. Calcola xor dei 98 elementi (dopo che i 2 elementi sono stati rimossi)
  3. Ora (risultato di 1) XOR (risultato di 2) ti dà lo xor dei due numeri mancanti i..ea XOR b se a e b sono gli elementi mancanti
    4. Ottieni la somma dei numeri mancanti con il tuo solito approccio al somma formula diff e diciamo che diff è d.

Ora esegui un ciclo per ottenere le possibili coppie (p, q) che si trovano entrambe in [1, 100] e somma in d.

Quando si ottiene una coppia, verificare se (risultato di 3) XOR p = q e se sì, abbiamo finito.

Per favore, correggimi se sbaglio e commenta anche la complessità temporale se questo è corretto


2
Non credo che la somma e xor definiscano in modo univoco due numeri. L'esecuzione di un ciclo per ottenere tutte le possibili k-tuple che sommano a d richiede tempo O (C (n, k-1)) = O (n <sup> k-1 </sup>), che, per k> 2, è cattivo.
Teepeemm,

0

Possiamo fare Q1 e Q2 in O (log n) il più delle volte.

Supponiamo che il nostro sia memory chipcostituito da un array di nnumero di test tubes. E un numero xnella provetta è rappresentato da x milliliterchimico-liquido.

Supponiamo che il nostro processore sia un laser light. Quando accendiamo il laser attraversa tutti i tubi perpendicolarmente alla sua lunghezza. Ogni volta che passa attraverso il liquido chimico, la luminosità viene ridotta di 1. E passare la luce a un certo millilitro è un'operazione di O(1).

Ora, se accendiamo il nostro laser al centro della provetta e otteniamo un risultato di luminosità

  • è uguale a un valore pre-calcolato (calcolato quando non mancava alcun numero), quindi i numeri mancanti sono maggiori di n/2.
  • Se il nostro output è più piccolo, allora c'è almeno un numero mancante che è più piccolo di n/2. Possiamo anche verificare se la luminosità è ridotta di 1o 2. se viene ridotto di 1allora un numero mancante è più piccolo di n/2e l'altro è più grande di n/2. Se viene ridotto di 2allora entrambi i numeri sono più piccoli di n/2.

Possiamo ripetere ancora e ancora il processo sopra indicato restringendo il nostro dominio problematico. In ogni passaggio, rendiamo il dominio più piccolo della metà. E finalmente possiamo arrivare al nostro risultato.

Algoritmi paralleli che vale la pena menzionare (perché sono interessanti),

  • l'ordinamento mediante un algoritmo parallelo, ad esempio, l'unione parallela può essere eseguita in O(log^3 n)tempo. E quindi il numero mancante può essere trovato dalla ricerca binaria nel O(log n)tempo.
  • Teoricamente, se abbiamo nprocessori, ogni processo può controllare uno degli input e impostare un flag che identifichi il numero (convenientemente in un array). E nel passaggio successivo ogni processo può controllare ogni flag e infine emettere il numero che non è contrassegnato. L'intero processo richiederà O(1)tempo. Ha O(n)requisiti di spazio / memoria aggiuntivi .

Si noti che i due algoritmi paralleli forniti sopra potrebbero richiedere spazio aggiuntivo, come indicato nel commento .


Mentre il metodo laser in provetta è davvero interessante, spero che tu accetti che non si traduce bene in istruzioni hardware e quindi è molto improbabile che si trovi O(logn)su un computer.
SirGuy,

1
Per quanto riguarda il tuo metodo di ordinamento, ci vorrà una quantità di spazio extra che dipende Ne più del O(N)tempo (in termini di dipendenza da N), che intendiamo fare meglio di.
SirGuy,

@SirGuy Apprezzo la tua preoccupazione per il concetto di provetta e il costo della memoria di elaborazione parallela. Il mio post è quello di condividere le mie opinioni sul problema. I processori GPU stanno ora rendendo possibile l'elaborazione parallela. Chissà se il concetto di provetta non sarà disponibile in futuro.
shuva,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.