O (nlogn) Algorithm - Trova tre spaziature uniformemente all'interno di una stringa binaria


173

Ieri ho fatto questa domanda durante un test sugli algoritmi e non riesco a capire la risposta. Mi sta facendo impazzire, perché valeva circa 40 punti. Immagino che la maggior parte della classe non l'abbia risolta correttamente, perché non ho trovato una soluzione nelle ultime 24 ore.

Data una stringa binaria arbitraria di lunghezza n, se ne esistono tre equidistanti all'interno della stringa. Scrivi un algoritmo che risolva questo problema nel tempo O (n * log (n)).

Quindi stringhe come queste ne hanno tre "equidistanti": 11100000, 0100100100

modifica: è un numero casuale, quindi dovrebbe essere in grado di funzionare per qualsiasi numero. Gli esempi che ho dato sono stati per illustrare la proprietà "equidistante". Quindi 1001011 è un numero valido. Con 1, 4 e 7 sono quelli che sono equidistanti.


4
È possibile: 10011010000? Ha tre 1 (primo, secondo, quarto) distribuiti uniformemente, ma ci sono anche altri 1.
Anna,

5
Robert, devi chiedere al tuo professore di darti la risposta e pubblicarla qui. Questo problema mi sta facendo salire sul muro. Posso capire come farlo in n ^ 2 ma non in n * log (n).
James McMahon,

3
Hmm ho passato molto tempo a cercare di capire anche questo, non ho ancora trovato una buona risposta. Forse hai frainteso la domanda? Ad esempio, se la domanda si pone, trova un algoritmo che gira in O (n log n) che determina la posizione di una sequenza uniformemente spaziata di spaziatura k, in una sequenza molto più ampia, questo potrebbe essere facilmente fatto usando la trasformata di Fourier veloce.
Cane

2
se il tuo prof offre una soluzione, inseriscilo come risposta.
Cane

5
Considerando il fatto che Klaus Roth ha ottenuto una medaglia Fields 1958 per (tra le altre cose) dimostrando che per ogni densità d> 0 esiste un numero naturale N tale che ogni sottoinsieme di {1, ..., N} con almeno d * N elementi contiene una progressione aritmetica di lunghezza 3, non mi sorprende che fino ad ora nessuno abbia trovato un algoritmo convincente per il problema. Vedi anche en.wikipedia.org/wiki/Szemer%C3%A9di%27s_theorem
jp

Risposte:


128

Finalmente! Seguendo i lead nella risposta di sdcvvc, ce l' abbiamo: l'algoritmo O (n log n) per il problema! È anche semplice, dopo averlo capito. Coloro che hanno indovinato la FFT avevano ragione.

Il problema: ci viene data una stringa binaria Sdi lunghezza n e vogliamo trovare tre 1 equidistanti. Ad esempio, Spuò essere 110110010, dove n = 9. Ha spaziato uniformemente 1s nelle posizioni 2, 5 e 8.

  1. Scansiona da Ssinistra a destra e crea un elenco Ldi posizioni di 1. Per quanto S=110110010sopra, abbiamo l'elenco L = [1, 2, 4, 5, 8]. Questo passaggio è O (n). Il problema ora è trovare una progressione aritmetica di lunghezza 3 in L, cioè trovare distinti a, b, c in modo Ltale che ba = cb , o equivalentemente a + c = 2b . Per l'esempio sopra, vogliamo trovare la progressione (2, 5, 8).

  2. Crea un polinomio p con i termini x k per ogni k in L. Per l'esempio sopra, realizziamo il polinomio p (x) = (x + x 2 + x 4 + x 5 + x 8 ) . Questo passaggio è O (n).

  3. Trova il polinomio q= p 2 , usando la Trasformata di Fourier veloce . Per l'esempio sopra, otteniamo il polinomio q (x) = x 16 + 2x 13 + 2x 12 + 3x 10 + 4x 9 + x 8 + 2x 7 + 4x 6 + 2x 5 + x 4 + 2x 3 + x 2 . Questo passaggio è O (n log n).

  4. Ignora tutti i termini tranne quelli corrispondenti a x 2k per alcuni k in L. Per l'esempio sopra, otteniamo i termini x 16 , 3x 10 , x 8 , x 4 , x 2 . Questo passaggio è O (n), se si sceglie di farlo affatto.

Ecco il punto cruciale: il coefficiente di ogni x 2b per b in Lè precisamente il numero di coppie (a, c) in modo Ltale che a + c = 2b . [CLRS, es. 30,1-7] Una di queste coppie è (b, b) sempre (quindi il coefficiente è almeno 1), ma se esiste un'altra coppia (a, c) , il coefficiente è almeno 3, da (a, c ) e (c, a) . Per l'esempio sopra, abbiamo il coefficiente di x 10 per essere 3 proprio a causa dell'AP (2,5,8). (Questi coefficienti x 2bsaranno sempre numeri dispari, per i motivi sopra. E tutti gli altri coefficienti in q saranno sempre pari.)

Quindi, l'algoritmo consiste nel guardare i coefficienti di questi termini x 2b e vedere se uno di essi è maggiore di 1. Se non ce n'è nessuno, allora non ci sono 1 spaziati uniformemente. Se v'è un b in Lper cui il coefficiente di x 2b è maggiore di 1, allora sappiamo che v'è una certa coppia (a, c) - diverso (b, b) - per cui a + c = 2b . Per trovare la coppia effettiva, proviamo semplicemente ciascuno a in L(la c corrispondente sarebbe 2b-a ) e vediamo se c'è un 1 nella posizione 2b-a in S. Questo passaggio è O (n).

È tutto gente.


Ci si potrebbe chiedere: dobbiamo usare FFT? Molte risposte, come beta , flybywire e rsp , suggeriscono che l'approccio che controlla ogni coppia di 1 e vede se c'è un 1 nella "terza" posizione, potrebbe funzionare in O (n log n), in base all'intuizione che se ci sono troppi 1, troveremmo facilmente una tripla e se ci sono troppi 1, controllare tutte le coppie richiede poco tempo. Sfortunatamente, sebbene questa intuizione sia corretta e l'approccio semplice sia migliore di O (n 2 ), non è significativamente migliore. Come nella risposta di sdcvvc , possiamo prendere il "set Cantor-like" di stringhe di lunghezza n = 3 k, con 1s nelle posizioni la cui rappresentazione ternaria contiene solo 0 e 2 (non 1). Una stringa del genere ha 2 k = n (log 2) / (log 3) ≈ n 0,63 in essa e nessuna spaziatura uniforme di 1s, quindi il controllo di tutte le coppie sarebbe dell'ordine del quadrato del numero di 1s in essa: questo è 4 k ≈ n 1,26 che purtroppo è asintoticamente molto più grande di (n log n). In effetti, il caso peggiore è anche peggiore: Leo Moser nel 1953 costruì (in modo efficace) stringhe che avevano n 1-c / √ (log n) 1s in esse, ma non 1 spaziate uniformemente, il che significa che su tali stringhe, il semplice l'approccio prenderebbe Θ (n 2-2c / √ (log n) )- solo un piccolo po 'meglio di Θ (n 2 ) , a sorpresa!


Circa il numero massimo di 1 in una stringa di lunghezza n con 3 non equidistanti (che abbiamo visto sopra era almeno n 0,63 dalla facile costruzione simile a Cantor, e almeno n 1-c / √ (log n) con Costruzione di Moser) - questa è OEIS A003002 . Può anche essere calcolato direttamente da OEIS A065825 come k tale che A065825 (k) ≤ n <A065825 (k + 1). Ho scritto un programma per trovarli, e si scopre che l'algoritmo avido non fornisce la stringa più lunga. Ad esempio, per n = 9, possiamo ottenere 5 1s (110100011) ma l'avido dà solo 4 (110110000), per n = 26 possiamo ottenere 11 1s (11001010001000010110001101) ma il goloso ne dà solo 8 (11011000011011000000000000), e per n= 74 possiamo ottenere 22 1s (1100001011000100000101101000100000000000000000001011010000010001101000011) ma il goloso dà solo 16 (1101100001101100000000000000011011000011011000000000000000000000000000000000). Sono d'accordo in parecchi posti fino al 50 (ad es. Da 38 a 50), comunque. Come dicono i riferimenti OEIS, sembra che Jaroslaw Wroblewski sia interessato a questa domanda e mantiene un sito Web su questi insiemi senza media . I numeri esatti sono noti solo fino a 194.


27
Molto bella. Degno di nota. Sembra un po 'aspettarsi che qualcuno riesca a risolverlo in un test.
hughdbrown,

4
Bene, il passaggio 1, traducendo il problema nella ricerca di un AP, è semplice. Il passaggio 3, che i polinomi possono essere moltiplicati nel tempo O (n log n), è solo un dato di fatto. Il vero trucco, e ciò che rende difficile il problema, è l'idea di pensare a 11011 come il polinomio con coefficienti [1,1,0,1,1], ecc. Questa è un'idea intelligente e spesso utile, che va tutto il ritorno a Eulero. [Vedi il fantastico libro di Wilf "Generationfunology" per un'esposizione moderna: math.upenn.edu/~wilf/DownldGF.html ] Quindi dipende se gli studenti sono stati esposti a generare funzioni nella memoria recente o meno. :-)
ShreevatsaR

2
Mi dispiace che il mio calcolo sia completamente sbagliato. Dovrebbe essere 110110010 ^ 2 = 12124214302200100. Ma l'idea è valida. Basta notare la posizione del 3.
Guillermo Phillips,

11
Molto impressionante. È davvero bello vedere questa discussione / domanda riunirsi e trovare una soluzione. Stavo iniziando a pensare che non fosse possibile. Inoltre, questo professore è malvagio.
KingNestor,

1
@RexE: se p è di grado n-1 (ha n termini), q = p ^ 2 è di grado 2n-2 (ha al massimo 2n-1 termini). Come hai ottenuto n ^ 2? (Inoltre, moltiplicare due polinomi di grado n in tempo O (n log n) usando la FFT è un'operazione abbastanza standard; fai clic sul link nella risposta o leggi l' articolo di Wikipedia .)
ShreevatsaR

35

Il tuo problema si chiama MEDIA in questo documento (1999):

Un problema è 3SUM-duro se c'è una riduzione sub-quadratica dal problema 3SUM: dato un insieme A di n numeri interi, ci sono elementi a, b, c in A tali che a + b + c = 0? Non è noto se AVERAGE sia 3SUM-hard. Tuttavia, esiste una semplice riduzione del tempo lineare da AVERAGE a 3SUM, di cui omettiamo la descrizione.

Wikipedia :

Quando gli interi sono nell'intervallo [−u ... u], 3SUM può essere risolto nel tempo O (n + u lg u) rappresentando S come vettore di bit ed eseguendo una convoluzione usando FFT.

Questo è abbastanza per risolvere il tuo problema :).

Ciò che è molto importante è che O (n log n) è la complessità in termini di numero di zero e uno, non il conteggio di uno (che potrebbe essere dato come un array, come [1,5,9,15]). Controllare se un set ha una progressione aritmetica, termini del numero di 1, è difficile, e secondo quel documento a partire dal 1999 non è noto alcun algoritmo più veloce di O (n 2 ), e si ipotizza che non esista. Chiunque non ne tenga conto, sta tentando di risolvere un problema aperto.

Altre informazioni interessanti, per lo più irriverenti:

Limite inferiore:

Un limite inferiore facile è un insieme simile a Cantor (numeri 1..3 ^ n-1 che non contengono 1 nella loro espansione ternaria) - la sua densità è n ^ (log_3 2) (circa 0,631). Quindi qualsiasi controllo se il set non è troppo grande, e quindi controllare tutte le coppie non è sufficiente per ottenere O (n log n). Devi investigare la sequenza in modo più intelligente. Una migliore limite inferiore è citato qui - è n 1-c / (log (n)) ^ (1/2) . Ciò significa che Cantor set non è ottimale.

Limite superiore - il mio vecchio algoritmo:

È noto che per n grande, un sottoinsieme di {1,2, ..., n} che non contiene progressione aritmetica ha al massimo n / (log n) ^ (1/20) elementi. L'articolo su triple in progressione aritmetica dimostra di più: il set non può contenere più di n * 2 28 * (log log n / log n) 1/2 elementi. Quindi è possibile verificare se tale limite viene raggiunto e, in caso contrario, controllare in modo ingenuo le coppie. Questo è l' algoritmo O (n 2 * log log n / log n), più veloce di O (n 2 ). Sfortunatamente "On triples ..." è su Springer - ma la prima pagina è disponibile e l'esposizione di Ben Green è disponibile qui , pagina 28, teorema 24.

A proposito, i documenti sono del 1999 - lo stesso anno del primo che ho citato, quindi è probabilmente per questo che il primo non menziona quel risultato.


2
Ottima risposta, la prima che dice qualcosa di definitivo su questo problema. Quindi l'insieme simile a Cantor ha n ^ 0.63 1s, il che significa che l'algoritmo "controlla tutte le coppie di 1s" è almeno n ^ 1,26 (log n log n) nel peggiore dei casi. Il limite inferiore citato nel documento di Szemeredi (BTW il documento Moser che cita è disponibile qui: books.google.com/books?id=Cvtwu5vVZF4C&pg=PA245 ) sembra implicare effettivamente n ^ (2-o (1)), ma dobbiamo fai un po 'attenzione perché abbiamo numeri estratti da {1, ..., n} ma qui è la somma dei numeri nella sequenza che è n.
ShreevatsaR,

Ehm, qual è esattamente la sequenza binaria "simile a Cantor" che contiene n ^ (log_3 2) 1s in esso e non tre 1s equidistanti?
ShreevatsaR,

Esempio: 101000101000000000101000101. La sua lunghezza è 3 ^ n e ne ha 2 ^ n (quindi n ^ 0,63 densità). Se annoti i posti di 1 in binario, sarà {0,2,20,22.200,202,220,222}. Un altro modo possibile di pensarci è prendere una sequenza di quelli e rimuovere continuamente quelli "medi" come nella normale costruzione del set Cantor: 111111111 -> 111000111 -> 101000101. Il motivo per cui non contiene progressione aritmetica è: se x , y, z ne formano uno, quindi y = (x + z) / 2 e xe z differiscono in un punto di espansione. Prendi quello più significativo. Dì che x ha 0 e che z ha 2. Quindi y deve avere 1 lì. contraddizione.
sdcvvc,

3
Ancora una volta, grande ricerca! Ho seguito il documento 3SUM del 2008, che faceva riferimento all'esercizio CLRS. 30.1-7, dopo aver visto la risposta, l'algoritmo O (n log n) è in realtà abbastanza semplice! (Basta quadrare un polinomio / funzione generatrice.) Ho pubblicato la risposta qui sotto. (Ora mi prendo a calci per non averci pensato prima ... soluzioni semplici suscitano sempre quella reazione: p)
ShreevatsaR

Quindi, la risposta alla sua domanda d'esame è stata qualcosa del tipo: "Questo problema è riducibile al problema 3-SUM difficile e 3-SUM difficile non ha una soluzione sub-quadratica, quindi questo problema non può essere risolto in O (n logn). " Sì?
hughdbrown,

8

Questa non è una soluzione, ma una linea di pensiero simile a quella che Olexiy stava pensando

Stavo giocando con la creazione di sequenze con il numero massimo di una, e sono tutte abbastanza interessanti, ho ottenuto fino a 125 cifre e qui ci sono i primi 3 numeri che ha trovato tentando di inserire il maggior numero di bit '1' possibile:

  • 11011000011011000000000000001101100001101100000000000000000000000000000000000000000110110000110110000000000000011011000011011
  • 10110100010110100000000000010110100010110100000000000000000000000000000000000000000101101000101101000000000000101101000101101
  • 10011001010011001000000000010011001010011001000000000000000000000000000000000000010011001010011001000000000010011001010011001

Si noti che sono tutti frattali (non troppo sorprendente dati i vincoli). Potrebbe esserci qualcosa nel pensare all'indietro, forse se la corda non è un frattale con una caratteristica, allora deve avere uno schema ripetitivo?

Grazie a beta per il termine migliore per descrivere questi numeri.

Aggiornamento: purtroppo sembra che il modello si interrompa quando si inizia con una stringa iniziale abbastanza grande, come ad esempio: 10000000000001:

100000000000011
10000000000001101
100000000000011011
10000000000001101100001
100000000000011011000011
10000000000001101100001101
100000000000011011000011010000000001
100000000000011011000011010000000001001
1000000000000110110000110100000000010011
1000000000000110110000110100000000010011001
10000000000001101100001101000000000100110010000000001
10000000000001101100001101000000000100110010000000001000001
1000000000000110110000110100000000010011001000000000100000100000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101
100000000000011011000011010000000001001100100000000010000010000000000000110100001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001000000000000000000000010010000010000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001000000000000000000000000000000000000110010000000000000000000000100100000100000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001000000110000000000001

2
Santo * @ !!, questi sono FRATTALI! Se questo regge, mette un limite superiore sul numero di 1 ed è inferiore a O (n).
Beta

frattali, è un termine molto migliore per descriverli. Grazie
z -

Interessante, questi schemi ricordano da vicino l'insieme ternario di Cantor ( en.wikipedia.org/wiki/Cantor_set ). Se è così, allora la proporzione di quelli deve tendere a zero ...
flybywire

È ovvio che le sequenze con il numero massimo di 1 s senza triple sono direttamente pertinenti al tempo di esecuzione dell'algoritmo nel caso peggiore? È concepibile che tu possa avere stringhe con molti 1 ma in cui trovi le triple solo molto tardi, dato che quelle 1 sono nelle posizioni che sono esaminate in ritardo dal tuo algoritmo.
ShreevatsaR,

3
La mia analisi del numero di uno nelle stringhe rispetto alla loro dimensione complessiva sembra indicare che esiste una relazione lineare tra numero di uno e dimensione della stringa, portandomi a credere che non ci sia un limite superiore felice che ci permetta di dire che il il numero di quelli in una stringa sarà al massimo log (n) per una determinata stringa. Quindi anche le soluzioni che si occupano solo delle posizioni di quelle e non dell'intera stringa saranno O (n ^ 2). O, più precisamente, O (n + m ^ 2), dove m è il numero di quelli nella stringa e n è la dimensione della stringa e m è big-theta (n).
Welbog,

6

Sospetto che un approccio semplice che assomigli a O (n ^ 2) produrrà effettivamente qualcosa di meglio, come O (n ln (n)). Le sequenze che richiedono più tempo per essere testate (per ogni dato n) sono quelle che non contengono trii e che impongono forti restrizioni al numero di 1 che possono essere presenti nella sequenza.

Ho escogitato alcuni argomenti agitando la mano, ma non sono stato in grado di trovare una prova ordinata. Prenderò una pugnalata nel buio: la risposta è un'idea molto intelligente che il professore conosce da così tanto tempo da sembrare ovvio, ma è troppo difficile per gli studenti. (O quello o hai dormito attraverso la lezione che lo ha coperto.)


2
lol, no, non ho dormito durante nessuna lezione. Ho parlato con alcuni altri studenti e nessuno ha avuto un'idea chiara su come risolverlo. La maggior parte ha scritto alcuni BS su divisione e conquista in un appello per ottenere un credito parziale.
Robert Parker,

3

Revisione: 2009-10-17 23:00

Ho eseguito questo su grandi numeri (come, stringhe di 20 milioni) e ora credo che questo algoritmo non sia O (n logn). Nonostante ciò, è un'implementazione abbastanza interessante e contiene una serie di ottimizzazioni che la rendono molto veloce. Valuta tutte le disposizioni delle stringhe binarie di 24 o meno cifre in meno di 25 secondi.

Ho aggiornato il codice per includere l' 0 <= L < M < U <= X-1osservazione di prima di oggi.


Originale

Questo è, in teoria, simile a un'altra domanda a cui ho risposto . Quel codice ha anche esaminato tre valori in una serie e determinato se una tripletta soddisfaceva una condizione. Ecco il codice C # adattato da quello:

using System;
using System.Collections.Generic;

namespace StackOverflow1560523
{
    class Program
    {
        public struct Pair<T>
        {
            public T Low, High;
        }
        static bool FindCandidate(int candidate, 
            List<int> arr, 
            List<int> pool, 
            Pair<int> pair, 
            ref int iterations)
        {
            int lower = pair.Low, upper = pair.High;
            while ((lower >= 0) && (upper < pool.Count))
            {
                int lowRange = candidate - arr[pool[lower]];
                int highRange = arr[pool[upper]] - candidate;
                iterations++;
                if (lowRange < highRange)
                    lower -= 1;
                else if (lowRange > highRange)
                    upper += 1;
                else
                    return true;
            }
            return false;
        }
        static List<int> BuildOnesArray(string s)
        {
            List<int> arr = new List<int>();
            for (int i = 0; i < s.Length; i++)
                if (s[i] == '1')
                    arr.Add(i);
            return arr;
        }
        static void BuildIndexes(List<int> arr, 
            ref List<int> even, ref List<int> odd, 
            ref List<Pair<int>> evenIndex, ref List<Pair<int>> oddIndex)
        {
            for (int i = 0; i < arr.Count; i++)
            {
                bool isEven = (arr[i] & 1) == 0;
                if (isEven)
                {
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count+1});
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count});
                    even.Add(i);
                }
                else
                {
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count+1});
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count});
                    odd.Add(i);
                }
            }
        }

        static int FindSpacedOnes(string s)
        {
            // List of indexes of 1s in the string
            List<int> arr = BuildOnesArray(s);
            //if (s.Length < 3)
            //    return 0;

            //  List of indexes to odd indexes in arr
            List<int> odd = new List<int>(), even = new List<int>();

            //  evenIndex has indexes into arr to bracket even numbers
            //  oddIndex has indexes into arr to bracket odd numbers
            List<Pair<int>> evenIndex = new List<Pair<int>>(), 
                oddIndex = new List<Pair<int>>(); 
            BuildIndexes(arr, 
                ref even, ref odd, 
                ref evenIndex, ref oddIndex);

            int iterations = 0;
            for (int i = 1; i < arr.Count-1; i++)
            {
                int target = arr[i];
                bool found = FindCandidate(target, arr, odd, oddIndex[i], ref iterations) || 
                    FindCandidate(target, arr, even, evenIndex[i], ref iterations);
                if (found)
                    return iterations;
            }
            return iterations;
        }
        static IEnumerable<string> PowerSet(int n)
        {
            for (long i = (1L << (n-1)); i < (1L << n); i++)
            {
                yield return Convert.ToString(i, 2).PadLeft(n, '0');
            }
        }
        static void Main(string[] args)
        {
            for (int i = 5; i < 64; i++)
            {
                int c = 0;
                string hardest_string = "";
                foreach (string s in PowerSet(i))
                {
                    int cost = find_spaced_ones(s);
                    if (cost > c)
                    {
                        hardest_string = s;
                        c = cost;
                        Console.Write("{0} {1} {2}\r", i, c, hardest_string);
                    }
                }
                Console.WriteLine("{0} {1} {2}", i, c, hardest_string);
            }
        }
    }
}

Le principali differenze sono:

  1. Ricerca esaustiva di soluzioni
    Questo codice genera un insieme di dati di potenza per trovare l'input più difficile da risolvere per questo algoritmo.
  2. Tutte le soluzioni rispetto alle più difficili da risolvere
    Il codice della domanda precedente ha generato tutte le soluzioni utilizzando un generatore Python. Questo codice mostra solo il più difficile per ogni lunghezza del motivo.
  3. Algoritmo di punteggio
    Questo codice controlla la distanza tra l'elemento centrale e il bordo sinistro e destro. Il codice Python ha verificato se una somma era superiore o inferiore a 0.
  4. Convergenza su un candidato
    Il codice corrente funziona dal centro verso il bordo per trovare un candidato. Il codice nel problema precedente ha funzionato dai bordi verso il centro. Quest'ultima modifica offre un notevole miglioramento delle prestazioni.
  5. Uso di pool pari e dispari
    In base alle osservazioni alla fine di questo articolo, il codice cerca coppie di numeri pari di coppie di numeri dispari per trovare L e U, mantenendo M fisso. Ciò riduce il numero di ricerche mediante informazioni pre-elaborazione. Di conseguenza, il codice utilizza due livelli di riferimento indiretto nel ciclo principale di FindCandidate e richiede due chiamate a FindCandidate per ciascun elemento intermedio: una volta per i numeri pari e una per quelli dispari.

L'idea generale è di lavorare sugli indici, non sulla rappresentazione grezza dei dati. Il calcolo di un array in cui compaiono gli 1 consente all'algoritmo di funzionare in tempo proporzionale al numero di 1 nei dati anziché in tempo proporzionale alla lunghezza dei dati. Questa è una trasformazione standard: crea una struttura di dati che consenta un funzionamento più rapido mantenendo l'equivalente del problema.

I risultati non sono aggiornati: rimossi.


Modifica: 16-10-2009 18:48

Sui dati di yx, a cui viene data una certa credibilità nelle altre risposte come rappresentante di dati concreti su cui calcolare, ottengo questi risultati ... li ho rimossi. Sono obsoleti.

Vorrei sottolineare che questi dati non sono i più difficili per il mio algoritmo, quindi ritengo errato il presupposto che i frattali di yx siano i più difficili da risolvere. Il caso peggiore per un particolare algoritmo, mi aspetto, dipenderà dall'algoritmo stesso e probabilmente non sarà coerente tra algoritmi diversi.


Modifica: 17-10-2009 13:30

Ulteriori osservazioni al riguardo.

Innanzitutto, converti la stringa di 0 e 1 in una matrice di indici per ogni posizione degli 1. Dì che la lunghezza di quell'array A è X. Quindi l'obiettivo è trovare

0 <= L < M < U <= X-1

tale che

A[M] - A[L] = A[U] - A[M]

o

2*A[M] = A[L] + A[U]

Poiché A [L] e A [U] si sommano a un numero pari, non possono essere (pari, dispari) o (dispari, pari). La ricerca di una partita potrebbe essere migliorata dividendo A [] in pool pari e dispari e cercando partite su A [M] nei pool di candidati pari e dispari a turno.

Tuttavia, credo sia più un'ottimizzazione delle prestazioni che un miglioramento algoritmico. Il numero di confronti dovrebbe scendere, ma l'ordine dell'algoritmo dovrebbe essere lo stesso.


Modifica 18-10-2009 00:45

Ancora un'altra ottimizzazione mi viene in mente, nella stessa vena di separare i candidati in pari e dispari. Poiché i tre indici devono essere aggiunti a un multiplo di 3 (a, a + x, a + 2x - mod 3 è 0, indipendentemente da a e x), puoi separare L, M e U nei loro valori mod 3 :

M  L  U
0  0  0
   1  2
   2  1
1  0  2
   1  1
   2  0
2  0  1
   1  0
   2  2

In effetti, potresti combinarlo con l'osservazione pari / dispari e separarli nei loro valori mod 6:

M  L  U
0  0  0
   1  5
   2  4
   3  3
   4  2
   5  1

e così via. Ciò fornirebbe un'ulteriore ottimizzazione delle prestazioni ma non una velocità algoritmica.


2

Non sono ancora riuscito a trovare la soluzione :(, ma ho alcune idee.

E se iniziassimo da un problema inverso: costruiamo una sequenza con il numero massimo di 1s e SENZA trii equidistanti. Se riesci a dimostrare che il numero massimo di 1s è o (n), puoi migliorare la tua stima ripetendo solo l'elenco di 1s.


Bene, il numero di 1 è certamente limitato da O (n). Non può essere O (n ** 2), giusto - il numero di 1 cresce più velocemente dei dati? La domanda importante è se il limite superiore è inferiore a quello.
hughdbrown,

Ho usato la piccola o non quella grande
Olexiy,

2

Questo può aiutare ....

Questo problema si riduce a quanto segue:

Data una sequenza di numeri interi positivi, trova una sottosequenza contigua partizionata in un prefisso e un suffisso tale che la somma del prefisso della sottosequenza sia uguale alla somma del suffisso della sottosequenza.

Ad esempio, data una sequenza di [ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ], troveremmo una sottosequenza di [ 3, 6, 5, 2, 2]con un prefisso di [ 3, 6 ]con prefisso sum di 9e un suffisso [ 5, 2, 2 ]con con suffix sum di 9.

La riduzione è la seguente:

Data una sequenza di zeri e uno, e partendo da quello più a sinistra, continua a spostarti a destra. Ogni volta che ne incontra un altro, registra il numero di mosse da quando è stato rilevato il precedente e aggiungi quel numero alla sequenza risultante.

Ad esempio, data una sequenza di [ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ], troveremmo la riduzione di [ 1, 3, 4]. Da questa riduzione, calcoliamo la sottosequenza contigua di [ 1, 3, 4], il prefisso di [ 1, 3]con la somma di 4e il suffisso di [ 4 ]con la somma di 4.

Questa riduzione può essere calcolata in O(n).

Sfortunatamente, non sono sicuro di dove andare da qui.


1
È una notazione più compatta, ma non aiuta la complessità temporale. L'insieme delle partizioni "prefisso" è isomorfo alla ricerca di tutte le coppie in tutte le occorrenze di "1", che è O (n ^ 2).
p00ya,

Apparentemente ci sono algoritmi là fuori che trattano somme di sottosequenza contigue. Sfortunatamente tutti sembrano avere a che fare con la ricerca della sottosequenza contigua con la somma massima in O (n).
yfeldblum,

@ p00ya questo non è corretto. Usando questo algoritmo la coplexità temporale dipende dal limite superiore del numero di falsi, che per assupton sulla stringa generata da Cantor è ((3/2) ^ (log (n) / log (3))) e la complessità dello spazio diventa questa, ma la complessità temporale si moltiplica per n. Controlla la mia seconda risposta. (non quello negativo): D
Luka Rahne,

@ralu: si presume che le stringhe generate da Cantor siano il caso peggiore, il che è sbagliato. Per la cronaca, il numero di coppie è sicuramente O (n ^ 2); ma suppongo che stavo davvero insinuando che fosse big-Omega (n ^ 2), il che non è corretto dati questi risultati (vedi il link NrootN in particolare), suggerendo un limite inferiore nelle coppie di big-Omega (n ^ (2 / 1.52 )) per prova o big-Omega (n ^ (4/3)) per congettura.
p00ya,

1

Per il tipo di problema semplice (cioè cerchi tre "1" con solo (cioè zero o più) "0" tra di esso), È abbastanza semplice: puoi semplicemente dividere la sequenza in corrispondenza di ogni "1" e cercare due sottosezioni adiacenti con la stessa lunghezza (la seconda sottosequenza non è l'ultima, ovviamente). Ovviamente, questo può essere fatto in O (n) tempo.

Per la versione più complessa (cioè cerchi un indice i e un gap g > 0 tale che s[i]==s[i+g]==s[i+2*g]=="1"), non sono sicuro, se esiste una soluzione O (n log n) , poiché probabilmente ci sono terzine O (n²) con questa proprietà (pensa a una stringa di tutte, ci sono circa n² / 2 di queste terzine). Certo, stai cercando solo uno di questi, ma al momento non ho idea di come trovarlo ...


Sì, stiamo discutendo la versione più difficile del problema. Tuttavia, la soluzione n * log (n) potrebbe essere possibile.
Olexiy,

1
in realtà ci sono n 3 che sono O (n ^ 3) possibili triple, penso che quando hai detto approssimativamente n ^
2/2

@gmatt: n scegliere 2 è sufficiente; se fissiamo due 1 la posizione del terzo viene determinata ed è tempo costante per vedere se c'è un 1 in quella posizione o meno.
ShreevatsaR,

@ShreevatsaR: sì, è vero, penso, stavo pensando al caso semplice.
Cane

1
@gmatt: in realtà, stiamo cercando Tuple (i, g) come definito sopra con i vincoli che 0 <= i <(n-3) e 0 <g <(ni-1) / 2, quindi la stima di n ^
2/2

1

Una domanda divertente, ma una volta che ti rendi conto che il modello reale tra due '1 non ha importanza, l'algoritmo diventa:

  • scan cerca un '1'
  • a partire dalla scansione della posizione successiva per un altro '1' (fino alla fine dell'array meno la distanza dal primo '1' corrente altrimenti il ​​terzo '1' sarebbe fuori limite)
  • se nella posizione del 2 ° '1' più la distanza dal primo 1 'si trova un terzo' 1 ', ne abbiamo uno spazio uniformemente.

Nel codice, JTest fashion, (Nota che questo codice non è stato scritto per essere il più efficiente e ho aggiunto alcuni println per vedere cosa succede.)

import java.util.Random;

import junit.framework.TestCase;

public class AlgorithmTest extends TestCase {

 /**
  * Constructor for GetNumberTest.
  *
  * @param name The test's name.
  */
 public AlgorithmTest(String name) {
  super(name);
 }

 /**
  * @see TestCase#setUp()
  */
 protected void setUp() throws Exception {
  super.setUp();
 }

 /**
  * @see TestCase#tearDown()
  */
 protected void tearDown() throws Exception {
  super.tearDown();
 }

 /**
  * Tests the algorithm.
  */
 public void testEvenlySpacedOnes() {

  assertFalse(isEvenlySpaced(1));
  assertFalse(isEvenlySpaced(0x058003));
  assertTrue(isEvenlySpaced(0x07001));
  assertTrue(isEvenlySpaced(0x01007));
  assertTrue(isEvenlySpaced(0x101010));

  // some fun tests
  Random random = new Random();

  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
 }

 /**
  * @param testBits
  */
 private boolean isEvenlySpaced(long testBits) {
  String testString = Long.toBinaryString(testBits);
  char[] ones = testString.toCharArray();
  final char ONE = '1';

  for (int n = 0; n < ones.length - 1; n++) {

   if (ONE == ones[n]) {
    for (int m = n + 1; m < ones.length - m + n; m++) {

     if (ONE == ones[m] && ONE == ones[m + m - n]) {
      System.out.println(" IS evenly spaced: " + testBits + '=' + testString);
      System.out.println("               at: " + n + ", " + m + ", " + (m + m - n));
      return true;
     }
    }
   }
  }

  System.out.println("NOT evenly spaced: " + testBits + '=' + testString);
  return false;
 }
}

4
Se non sbaglio, questo è O (n²) perché il ciclo esterno viene eseguito n volte e il ciclo interno viene eseguito n / 2 volte in media.
StriplingWarrior,

Il loop esterno viene eseguito n volte e il loop interno esegue n / 4 in media ma viene avviato solo da posizioni che seguono un '1'. Per avvicinarsi a un comportamento n ^ 2, il numero di '1' deve essere elevato, il che si traduce in un risultato reale all'inizio, interrompendo così l'elaborazione. Pertanto il comportamento n ^ 2 non si verificherà mai. Come determinare una O in base alle proprietà note dei dati mi sfugge al momento.
rsp,

Sfortunatamente non si tratta di runtime medio nella vita reale ma piuttosto di teorico Big Runtime. E il tuo approccio è O (n²) (uguale al mio perché il tuo approccio è uguale al mio)
DaClown,

Non stavo parlando del comportamento medio, ma del massimo comportamento. Non sarei sorpreso se è dimostrabile che l'entropia massima che fallisce il test contiene log n '1 nella stringa.
rsp

Che cosa succede se aggiorni l'indice nel ciclo esterno con quello del primo 1 trovato nel ciclo interno, ovvero se (ones [uni] == UNO) {n = m}? Questo aiuta la grande O?
piroscafo25

1

Ho pensato a un approccio di divisione e conquista che potesse funzionare.

Innanzitutto, nella preelaborazione è necessario inserire tutti i numeri meno della metà della dimensione di input ( n / 3) in un elenco.

Dato una stringa: 0000010101000100(nota che questo esempio particolare è valido)

Inserisci tutti i numeri primi (e 1) da 1 a (16/2) in un elenco: {1, 2, 3, 4, 5, 6, 7}

Quindi dividerlo a metà:

100000101 01000100

Continuare a farlo fino a quando non si arriva a stringhe di dimensione 1. Per tutte le stringhe di dimensione 1 con un 1 in esse, aggiungere l'indice della stringa all'elenco di possibilità; in caso contrario, restituire -1 per errore.

Dovrai anche restituire un elenco di distanze di spaziatura ancora possibili, associate a ciascun indice iniziale. (Inizia con l'elenco che hai creato sopra e rimuovi i numeri mentre vai). Qui, un elenco vuoto significa che hai a che fare solo con 1 e quindi a questo punto è possibile qualsiasi spaziatura; in caso contrario l'elenco include spaziature che devono essere escluse.

Quindi continuando con l'esempio sopra:

1000 0101 0100 0100

10 00 01 01 01 00 01 00

1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 0

Nella prima fase della combinazione, ora abbiamo otto serie da due. Nel primo, abbiamo la possibilità di un set, ma apprendiamo che la spaziatura di 1 è impossibile a causa dell'altro zero presente. Quindi restituiamo 0 (per l'indice) e {2,3,4,5,7} per il fatto che la spaziatura di 1 è impossibile. Nel secondo, non abbiamo nulla e quindi restituiamo -1. Nel terzo abbiamo una partita senza spaziature eliminate nell'indice 5, quindi restituiamo 5, {1,2,3,4,5,7}. Nella quarta coppia restituiamo 7, {1,2,3,4,5,7}. Nel quinto, restituisce 9, {1,2,3,4,5,7}. Nel sesto, ritorna -1. Nel settimo, ritorno 13, {1,2,3,4,5,7}. Nell'ottavo, ritorna -1.

Combinando nuovamente in quattro serie da quattro, abbiamo:

1000: Return (0, {4,5,6,7}) 0101: Return (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6 , 7}) 0100: Return (9, {3,4,5,6,7}) 0100: Return (13, {3,4,5,6,7})

Combinando in serie di otto:

10000101: Return (0, {5,7}), (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7}) 01000100: Return (9, {4,7}), (13, {3,4,5,6,7})

Combinando in un set di sedici:

10000101 01000100

Mentre procediamo, continuiamo a verificare tutte le possibilità finora. Fino a questo passaggio abbiamo lasciato cose che sono andate oltre la fine della stringa, ma ora possiamo verificare tutte le possibilità.

Fondamentalmente, controlliamo il primo 1 con spaziature di 5 e 7 e scopriamo che non si allineano a 1. (Nota che ogni controllo è COSTANTE, non tempo lineare) Quindi controlliamo il secondo (indice 5) con spaziature di 2, 3, 4, 5, 6 e 7-- o vorremmo, ma possiamo fermarci a 2 poiché che corrisponde davvero.

Accidenti! È un algoritmo piuttosto lungo.

Non so al 100% se è O (n log n) a causa dell'ultimo passaggio, ma tutto quello che c'è è sicuramente O (n log n) per quanto ne so. Tornerò su questo più tardi e proverò a perfezionare l'ultimo passaggio.

EDIT: ho cambiato la mia risposta per riflettere il commento di Welbog. Ci scusiamo per l'errore. Scriverò anche qualche pseudocodice, quando avrò un po 'più di tempo per decifrare ciò che ho scritto di nuovo. ;-)


Non seguo il tuo algoritmo, ma +1 per aver provato un algoritmo che in realtà cerca di essere O (n log n)
ldog

Grazie. Cercherò di spiegarlo meglio quando avrò più tempo (magari scrivere qualche pseudocodice o qualcosa del genere).
Platinum Azure,

Perché stai solo guardando le possibilità di gap dei numeri primi? Come proporresti di abbinare una stringa come 100010001? Se capisco correttamente il tuo approccio, non sarà in grado di abbinarlo perché (0,{4})non è possibile calcolare la risposta corretta . Dato che hai bisogno di non-primi nella tua lista, è facile trovare stringhe patologiche che gonfiano gli elenchi di possibilità che devi controllare più in alto di O (n log (n)), penso.
Welbog,

giura Beh, inizialmente avrei fatto dei multipli, ma ho cambiato la mia risposta a metà strada e non sono riuscito a cambiare tutto. Scusate. Risolverà a breve
Platinum Azure,

3
Non credo sia O (n log n). Nella prima fase della combinazione, vengono trattati (n / 2) insiemi, ciascuno dei quali può eventualmente restituire un insieme di O (n) possibili spaziature. Questo da solo lo rende O (n ^ 2), sfortunatamente.
MartinStettner,

1

Darò la mia ipotesi approssimativa qui, e permetterò a coloro che sono migliori nel calcolare la complessità di aiutarmi su come il mio algoritmo costa in notazione O

  1. data stringa binaria 0000010101000100 (come esempio)
  2. testa di taglio e coda di zeri -> 00000 101010001 00
  3. otteniamo 101010001 dal calcolo precedente
  4. controlla se il bit intermedio è 'uno', se vero, trova validi tre 'uno' equidistanti (solo se il numero di bit è dispari)
  5. correlativamente, se il numero di bit ritagliati rimanenti è pari, la testa e la coda "uno" non possono far parte di uno "uniformemente",
  6. usiamo 1010100001 come esempio (con un 'zero' in più per diventare il raccolto pari), in questo caso dobbiamo ritagliare di nuovo, quindi diventa -> 10101 00001
  7. otteniamo 10101 dal calcolo precedente e controlliamo il bit centrale e abbiamo trovato di nuovo il bit spaziato uniformemente

Non ho idea di come calcolare la complessità per questo, qualcuno può aiutare?

modifica: aggiungi del codice per illustrare la mia idea

edit2: ho provato a compilare il mio codice e ho riscontrato alcuni errori importanti, risolti

char *binaryStr = "0000010101000100";

int main() {
   int head, tail, pos;
   head = 0;
   tail = strlen(binaryStr)-1;
   if( (pos = find3even(head, tail)) >=0 )
      printf("found it at position %d\n", pos);
   return 0;
}

int find3even(int head, int tail) {
   int pos = 0;
   if(head >= tail) return -1;
   while(binaryStr[head] == '0') 
      if(head<tail) head++;
   while(binaryStr[tail] == '0') 
      if(head<tail) tail--;
   if(head >= tail) return -1;
   if( (tail-head)%2 == 0 && //true if odd numbered
       (binaryStr[head + (tail-head)/2] == '1') ) { 
         return head;
   }else {
      if( (pos = find3even(head, tail-1)) >=0 )
         return pos;
      if( (pos = find3even(head+1, tail)) >=0 )
         return pos;
   }
   return -1;
}

@recursive penso che funzionerà quando ha raggiunto la chiamata find3even (head + 1, tail), che poi lo ritaglierà per diventare 111 a head = 4, potresti controllare di nuovo per me?
andycjw,

@recursive, controlla il codice che ho aggiunto per spiegare meglio lo pseudo codice che ho creato in precedenza, che non è molto rigoroso e conciso
andycjw,

Questo è nlogn - per n bit ci aspettiamo approssimativamente iterazioni di logn che controllano n * c bit in cui C è una costante.
Ron Warholic,

Sì, questo sembra fallire su 111001 e 100111 come i casi più semplici. Gli 1 con spaziatura uniforme non devono centrarsi sul bit centrale.
Decano J

Gestisce questi casi correttamente, 111001 ha un numero pari di bit, quindi viene immediatamente suddiviso in 111 e 001. Poiché 111 ha un numero dispari di bit e il bit centrale è uno che restituisce correttamente.
Ron Warholic,

1

Ho pensato a qualcosa del genere:

def IsSymetric(number):
    number = number.strip('0')

    if len(number) < 3:
        return False
    if len(number) % 2 == 0:
        return IsSymetric(number[1:]) or IsSymetric(number[0:len(number)-2])
    else:
        if number[len(number)//2] == '1':
            return True
        return IsSymetric(number[:(len(number)//2)]) or IsSymetric(number[len(number)//2+1:])
    return False

Questo è ispirato da andycjw.

  1. Tronca gli zeri.
  2. Se anche allora prova due sottostringhe 0 - (len-2) (salta l'ultimo carattere) e da 1 - (len-1) (salta il primo carattere)
  3. Se non addirittura che se il carattere medio è uno di quello che abbiamo successo. Altrimenti dividi la corda nel midle senza l'elemento midle e controlla entrambe le parti.

Per quanto riguarda la complessità, questo potrebbe essere O (nlogn) poiché in ogni ricorsione stiamo dividendo per due.

Spero che sia d'aiuto.


Sembra che tu stia convertendo un problema con N elementi in 2 problemi con N-1 elementi. Dividerlo a metà significherebbe convertirlo in 2 problemi con N / 2 elementi.
RHSeeger,

Questo è solo il caso di lunghezze pari. Quindi se la len è 8 l'algoritmo crea stringhe di lunghezza: 7, 7, 3, 3, 3, 3. L'altezza dell'albero di ricorsione è 3 e che equivale a lg (8).
Beku,

1

Ok, ho intenzione di dare un'altra pugnalata al problema. Penso di poter dimostrare un algoritmo O (n log (n)) simile a quelli già discussi usando un albero binario bilanciato per memorizzare le distanze tra 1. Questo approccio è stato ispirato dall'osservazione di Justice sulla riduzione del problema a un elenco di distanze tra gli 1.

Potremmo scansionare la stringa di input per costruire un albero binario bilanciato attorno alla posizione di 1 in modo tale che ciascun nodo memorizzi la posizione di 1 e ogni bordo sia etichettato con la distanza dall'1 adiacente per ciascun nodo figlio. Per esempio:

10010001 gives the following tree

      3
     / \
  2 /   \ 3
   /     \
  0       7

Questo può essere fatto in O (n log (n)) poiché, per una stringa di dimensioni n, ogni inserimento prende O (log (n)) nel peggiore dei casi.

Quindi il problema è cercare l'albero per scoprire se, in qualsiasi nodo, esiste un percorso da quel nodo attraverso il figlio sinistro che ha la stessa distanza di un percorso attraverso il figlio destro. Questo può essere fatto in modo ricorsivo su ogni sottostruttura. Quando si uniscono due sottostrutture nella ricerca, dobbiamo confrontare le distanze dai tracciati nella sottostruttura sinistra con le distanze dai tracciati a destra. Poiché il numero di percorsi in una sottostruttura sarà proporzionale a log (n) e il numero di nodi è n, credo che ciò possa essere fatto in O (n log (n)) tempo.

Mi sono perso qualcosa?


"Poiché il numero di percorsi in una sottostruttura sarà proporzionale a log (n)" Perché non n? Generalmente questo è un approccio promettente.
sdcvvc,

@sdcwc: è proporzionale a log (n) e non n perché in un albero bilanciato ogni sottostruttura ha la metà dei nodi e il numero di percorsi verso la radice della sottostruttura è uguale al numero di nodi nella sottostruttura (escluso il radice).
Jeremy Bourque,

0

Sembrava un problema divertente, così ho deciso di provare.

Sto assumendo che 111000001 troverebbe i primi 3 e avrebbe avuto successo. Fondamentalmente il numero di zeri che seguono 1 è la cosa importante, poiché 0111000 è lo stesso di 111000 secondo la tua definizione. Una volta trovati due casi di 1, il successivo 1 trovato completa la trilogia.

Eccolo in Python:

def find_three(bstring):
    print bstring
    dict = {}
    lastone = -1
    zerocount = 0
    for i in range(len(bstring)):
        if bstring[i] == '1':
            print i, ': 1'
            if lastone != -1:
                if(zerocount in dict):
                    dict[zerocount].append(lastone)
                    if len(dict[zerocount]) == 2:
                        dict[zerocount].append(i)
                        return True, dict
                else:
                    dict[zerocount] = [lastone]
            lastone = i
            zerocount = 0
        else:
            zerocount = zerocount + 1
    #this is really just book keeping, as we have failed at this point
    if lastone != -1:
        if(zerocount in dict):
            dict[zerocount].append(lastone)
        else:
            dict[zerocount] = [lastone]
    return False, dict

Questo è un primo tentativo, quindi sono sicuro che potrebbe essere scritto in modo più pulito. Elencare di seguito i casi in cui questo metodo fallisce.


@recursive, quelli non sono equidistanti.
James McMahon,

Cosa intendi con spaziatura uniforme? Guarda l'indice 0, 3 e 6. Tutti e due separati ciascuno.
ricorsivo il

Oh, vedo, come ho capito, gli zeri sono stati inclusi solo nella spaziatura.
James McMahon,

La domanda menziona "1001011", su cui non funziona. C'era una risposta precedente (ora eliminata), pubblicata immediatamente dopo la domanda, che risolveva lo stesso (altro) problema di questo. :-)
ShreevatsaR

Oggi stavo guardando questo al lavoro e non capivo cosa intendesse Rob con la sua modifica. Ho modificato la domanda per chiarezza. Avrei dovuto sapere che mi mancava qualcosa quando mi sono divertito.
James McMahon,

0

Presumo che il motivo per cui questo è nlog (n) sia dovuto al seguente:

  • Per trovare l'1 che è l'inizio della terzina, è necessario controllare i caratteri (n-2). Se non l'hai trovato a quel punto, non lo farai (i caratteri n-1 e n non possono iniziare una tripletta) (O (n))
  • Per trovare il secondo 1 che è la parte della tripletta (iniziata dalla prima), è necessario controllare i caratteri m / 2 (m = nx, dove x è l'offset del primo 1) caratteri. Questo perché, se non hai trovato il secondo 1 quando sei a metà strada dal primo alla fine, non lo farai ... poiché il terzo 1 deve essere esattamente alla stessa distanza oltre il secondo. (O (log (n)))
  • È O (1) trovare l'ultimo 1 poiché si conosce l'indice che deve essere quando si trovano il primo e il secondo.

Quindi, hai n, log (n) e 1 ... O (nlogn)

Modifica: Oops, mio ​​cattivo. Il mio cervello aveva impostato che n / 2 era logn ... che ovviamente non lo è (il raddoppio del numero sugli elementi raddoppia ancora il numero di iterazioni sul ciclo interno). Questo è ancora a n ^ 2, non risolvendo il problema. Beh, almeno devo scrivere un po 'di codice :)


Implementazione in Tcl

proc get-triplet {input} {
    for {set first 0} {$first < [string length $input]-2} {incr first} {
        if {[string index $input $first] != 1} {
            continue
        }
        set start [expr {$first + 1}]
        set end [expr {1+ $first + (([string length $input] - $first) /2)}]
        for {set second $start} {$second < $end} {incr second} {
            if {[string index $input $second] != 1} {
                continue
            }
            set last [expr {($second - $first) + $second}]
            if {[string index $input $last] == 1} {
                return [list $first $second $last]
            }
        }
    }
    return {}
}

get-triplet 10101      ;# 0 2 4
get-triplet 10111      ;# 0 2 4
get-triplet 11100000   ;# 0 1 2
get-triplet 0100100100 ;# 1 4 7

0

Penso di aver trovato il modo di risolvere il problema, ma non posso costruire una prova formale. La soluzione che ho realizzato è scritta in Java e utilizza un contatore 'n' per contare il numero di accessi elenco / array che esegue. Quindi n dovrebbe essere minore o uguale a stringLength * log (stringLength) se è corretto. L'ho provato per i numeri da 0 a 2 ^ 22 e funziona.

Si inizia ripetendo la stringa di input e creando un elenco di tutti gli indici che ne contengono uno. Questo è solo O (n).

Quindi dall'elenco degli indici seleziona un firstIndex e un secondoIndex che è maggiore del primo. Questi due indici devono contenere quelli, perché si trovano nell'elenco degli indici. Da lì è possibile calcolare il terzo indice. Se inputString [thirdIndex] è un 1, si arresta.

public static int testString(String input){
//n is the number of array/list accesses in the algorithm
int n=0;

//Put the indices of all the ones into a list, O(n)
ArrayList<Integer> ones = new ArrayList<Integer>();
for(int i=0;i<input.length();i++){
    if(input.charAt(i)=='1'){
        ones.add(i);
    }
}

//If less than three ones in list, just stop
if(ones.size()<3){
    return n;
}

int firstIndex, secondIndex, thirdIndex;
for(int x=0;x<ones.size()-2;x++){
    n++;
    firstIndex = ones.get(x);

    for(int y=x+1; y<ones.size()-1; y++){
        n++;
        secondIndex = ones.get(y);
        thirdIndex = secondIndex*2 - firstIndex;

        if(thirdIndex >= input.length()){
            break;
        }

        n++;
        if(input.charAt(thirdIndex) == '1'){
            //This case is satisfied if it has found three evenly spaced ones
            //System.out.println("This one => " + input);
            return n;
        }
    }
}

return n;

}

nota aggiuntiva: il contatore n non viene incrementato quando scorre sulla stringa di input per costruire l'elenco di indici. Questa operazione è O (n), quindi non avrà comunque alcun effetto sulla complessità dell'algoritmo.


Sembra che tu abbia ancora due anelli di O (n), nidificati, che lo rende O (n ^ 2)
RHSeeger

La matrice di indici non ha le stesse dimensioni della stringa di input. Questo mi sta rendendo difficile scrivere una prova reale o dimostrare che non è corretto. Ho il sospetto che ci sia qualche idea matematica sottostante che fa funzionare questo.
Robert Parker,

1
Penso che il trucco di questo problema sia che nonostante il tuo algoritmo sia O (n ^ 2), il caso peggiore possibile di una stringa che puoi ottenere provocherà solo iterazioni O (nlogn) altrimenti avrai trovato una soluzione usando il tuo algoritmo.
z -

2
testarlo fino a 2 ^ 22 non prova davvero la sua complessità. 2 ^ 22 ha solo 22 bit, il che significa che N è 22. Provalo per alcuni valori in cui N è qualche milione.
Peter Recore,

1
Prova questo algoritmo con una delle stringhe "cattive" massime fornite nella risposta di yx e scoprirai che si tratta di un O(n^2)algoritmo.
Welbog,

0

Una via d'uscita al problema è pensare a fattori e cambiamenti.

Con lo spostamento, si confronta la stringa di uno e zero con una versione spostata di se stesso. Quindi prendi quelli corrispondenti. Prendi questo esempio spostato di due:

1010101010
  1010101010
------------
001010101000

Gli 1 risultanti (AND bit per bit), devono rappresentare tutti quegli 1 che sono equidistanti da due. Lo stesso esempio spostato di tre:

1010101010
   1010101010
-------------
0000000000000

In questo caso non ci sono 1 che sono equidistanti tra loro tre.

Cosa ti dice questo? Bene, devi solo testare i turni che sono numeri primi. Ad esempio, supponi di avere due 1 separati da sei. Dovresti solo testare "due" turni e "tre" turni (poiché questi dividono sei). Per esempio:

10000010 
  10000010 (Shift by two)
    10000010
      10000010 (We have a match)

10000010
   10000010 (Shift by three)
      10000010 (We have a match)

Quindi gli unici turni che devi mai controllare sono 2,3,5,7,11,13 ecc. Fino al primo più vicino alla radice quadrata della dimensione della stringa di cifre.

Quasi risolto?

Penso di essere più vicino a una soluzione. Fondamentalmente:

  1. Scansiona la stringa per 1. Per ogni 1 nota è resto dopo aver preso un modulo della sua posizione. Il modulo varia da 1 a metà della dimensione della stringa. Questo perché la massima dimensione di separazione possibile è metà della stringa. Questo viene fatto in O (n ^ 2). MA. Solo i moduli primi devono essere controllati, quindi O (n ^ 2 / log (n))
  2. Ordinare l'elenco di moduli / resti nell'ordine prima del modulo più grande, questo può essere fatto in O (n * log (n)) tempo.
  3. Cerca tre moduli / resti consecutivi che sono gli stessi.
  4. In qualche modo recuperare la posizione di quelli!

Penso che il più grande indizio della risposta sia che gli algoritmi di ordinamento più veloci siano O (n * log (n)).

SBAGLIATO

Il passaggio 1 è errato, come sottolineato da un collega. Se abbiamo 1 in posizione 2,12 e 102. Quindi prendendo un modulo di 10, avrebbero tutti gli stessi resti, e tuttavia non sono equamente distanziati! Scusate.


Questo è un approccio interessante, facci sapere se trovi una soluzione completa.
James McMahon,

shift di un numero k O (n) volte e quindi i controlli O (n) per shift producono un algoritmo O (n ^ 2), anche se si spostava di un numero. Il tuo algoritmo dovrebbe spostarsi di più di un numero.
Cane

0

Ecco alcuni pensieri che, nonostante i miei migliori sforzi, non sembrano avvolgersi in un arco. Tuttavia, potrebbero essere un utile punto di partenza per l'analisi di qualcuno.

Considera la soluzione proposta come segue, che è l'approccio suggerito da molte persone, incluso me stesso in una versione precedente di questa risposta. :)

  1. Taglia zeri iniziali e finali.
  2. Scansiona la stringa cercando 1.
  3. Quando viene trovato un 1:
    1. Supponiamo che sia l'1 centrale della soluzione.
    2. Per ogni 1 precedente, usa la sua posizione salvata per calcolare la posizione prevista dell'1 finale.
    3. Se la posizione calcolata è successiva alla fine della stringa, non può far parte della soluzione, quindi eliminare la posizione dall'elenco dei candidati.
    4. Controlla la soluzione
  4. Se la soluzione non è stata trovata, aggiungere l'attuale 1 all'elenco dei candidati.
  5. Ripetere fino a quando non vengono trovati più 1.

Ora considera stringhe di input stringhe come le seguenti, che non avranno una soluzione:

101
101001
1010010001
101001000100001
101001000100001000001

In generale, questa è la concatenazione di k stringhe della forma j 0 seguita da un 1 per j da zero a k-1.

k=2  101
k=3  101001
k=4  1010010001
k=5  101001000100001
k=6  101001000100001000001

Si noti che le lunghezze delle sottostringhe sono 1, 2, 3, ecc. Quindi, la dimensione del problema n ha sottostringhe di lunghezze da 1 a k tali che n = k (k + 1) / 2.

k=2  n= 3  101
k=3  n= 6  101001
k=4  n=10  1010010001
k=5  n=15  101001000100001
k=6  n=21  101001000100001000001

Nota che k tiene traccia anche del numero di 1 che dobbiamo considerare. Ricorda che ogni volta che vediamo un 1, dobbiamo considerare tutti gli 1 visti finora. Quindi quando vediamo il secondo 1, consideriamo solo il primo, quando vediamo il terzo 1, riconsideriamo i primi due, quando vediamo il quarto 1, dobbiamo riconsiderare i primi tre e così via. Alla fine dell'algoritmo, abbiamo considerato k (k-1) / 2 coppie di 1. Chiamalo p.

k=2  n= 3  p= 1  101
k=3  n= 6  p= 3  101001
k=4  n=10  p= 6  1010010001
k=5  n=15  p=10  101001000100001
k=6  n=21  p=15  101001000100001000001

La relazione tra n e p è che n = p + k.

Il processo di attraversamento della stringa richiede O (n) tempo. Ogni volta che si incontra un 1, viene effettuato un massimo di (k-1) confronti. Poiché n = k (k + 1) / 2, n> k ** 2, quindi sqrt (n)> k. Questo ci dà O (n sqrt (n)) o O (n ** 3/2). Si noti tuttavia che potrebbe non essere un limite molto stretto, poiché il numero di confronti va da 1 a un massimo di k, non è k per tutto il tempo. Ma non sono sicuro di come spiegarlo in matematica.

Non è ancora O (n log (n)). Inoltre, non posso dimostrare che gli input siano i casi peggiori, anche se sospetto che lo siano. Penso che un impaccamento più denso di 1 davanti produca un impacchettamento ancora più scarso alla fine.

Dato che qualcuno potrebbe ancora trovarlo utile, ecco il mio codice per quella soluzione in Perl:

#!/usr/bin/perl

# read input as first argument
my $s = $ARGV[0];

# validate the input
$s =~ /^[01]+$/ or die "invalid input string\n";

# strip leading and trailing 0's
$s =~ s/^0+//;
$s =~ s/0+$//;

# prime the position list with the first '1' at position 0
my @p = (0);

# start at position 1, which is the second character
my $i = 1;

print "the string is $s\n\n";

while ($i < length($s)) {
   if (substr($s, $i, 1) eq '1') {
      print "found '1' at position $i\n";
      my @t = ();
      # assuming this is the middle '1', go through the positions
      # of all the prior '1's and check whether there's another '1'
      # in the correct position after this '1' to make a solution
      while (scalar @p) {
         # $p is the position of the prior '1'
         my $p = shift @p;
         # $j is the corresponding position for the following '1'
         my $j = 2 * $i - $p;
         # if $j is off the end of the string then we don't need to
         # check $p anymore
         next if ($j >= length($s));
         print "checking positions $p, $i, $j\n";
         if (substr($s, $j, 1) eq '1') {
            print "\nsolution found at positions $p, $i, $j\n";
            exit 0;
         }
         # if $j isn't off the end of the string, keep $p for next time
         push @t, $p;
      }
      @p = @t;
      # add this '1' to the list of '1' positions
      push @p, $i;
   }
   $i++;
}

print "\nno solution found\n";

La sequenza "non soluzione" è errata; l'indice di ogni 1 è la sequenza di numeri triangolari 1, 3, 6, 10, 15 ... ecc. e include i numeri 6, 36 e 66, che formano una progressione aritmetica.
Jason S,

0

Durante la scansione di 1, aggiungi le loro posizioni a un elenco. Quando si aggiungono il secondo e i successivi 1, confrontarli con ciascuna posizione nell'elenco finora. La spaziatura è uguale a currentOne (al centro) - previousOne (a sinistra). Il bit lato destro è currentOne + spaziatura. Se è 1, la fine.

L'elenco di quelli cresce inversamente con lo spazio tra loro. Detto semplicemente, se hai molti 0 tra gli 1 (come nel caso peggiore), la tua lista di 1 noti crescerà abbastanza lentamente.

using System;
using System.Collections.Generic;

namespace spacedOnes
{
    class Program
    {
        static int[] _bits = new int[8] {128, 64, 32, 16, 8, 4, 2, 1};

        static void Main(string[] args)
        {
            var bytes = new byte[4];
            var r = new Random();
            r.NextBytes(bytes);
            foreach (var b in bytes) {
                Console.Write(getByteString(b));
            }
            Console.WriteLine();
            var bitCount = bytes.Length * 8;
            var done = false;
            var onePositions = new List<int>();
            for (var i = 0; i < bitCount; i++)
            {
                if (isOne(bytes, i)) {
                    if (onePositions.Count > 0) {
                        foreach (var knownOne in onePositions) {
                            var spacing = i - knownOne;
                            var k = i + spacing;
                            if (k < bitCount && isOne(bytes, k)) {
                                Console.WriteLine("^".PadLeft(knownOne + 1) + "^".PadLeft(spacing) + "^".PadLeft(spacing));
                                done = true;
                                break;
                            }
                        }
                    }
                    if (done) {
                        break;
                    }
                    onePositions.Add(i);
                }
            }
            Console.ReadKey();
        }

        static String getByteString(byte b) {
            var s = new char[8];
            for (var i=0; i<s.Length; i++) {
                s[i] = ((b & _bits[i]) > 0 ? '1' : '0');
            }
            return new String(s);
        }

        static bool isOne(byte[] bytes, int i)
        {
            var byteIndex = i / 8;
            var bitIndex = i % 8;
            return (bytes[byteIndex] & _bits[bitIndex]) > 0;
        }
    }
}

0

Ho pensato di aggiungere un commento prima di pubblicare la 22a soluzione ingenua al problema. Per la soluzione ingenua, non è necessario mostrare che il numero di 1 nella stringa sia al massimo O (log (n)), ma piuttosto che sia al massimo O (sqrt (n * log (n)).

Risolutore:

def solve(Str):
    indexes=[]
    #O(n) setup
    for i in range(len(Str)):
        if Str[i]=='1':
            indexes.append(i)

    #O((number of 1's)^2) processing
    for i in range(len(indexes)):
        for j in range(i+1, len(indexes)):
                            indexDiff = indexes[j] - indexes[i]
            k=indexes[j] + indexDiff
            if k<len(Str) and Str[k]=='1':
                return True
    return False

Fondamentalmente è un po 'simile all'idea e all'implementazione di flybywire, anche se guardando avanti invece che indietro.

Greedy String Builder:

#assumes final char hasn't been added, and would be a 1 
def lastCharMakesSolvable(Str):
    endIndex=len(Str)
    j=endIndex-1
    while j-(endIndex-j) >= 0:
        k=j-(endIndex-j)
        if k >= 0 and Str[k]=='1' and Str[j]=='1':
            return True
        j=j-1
    return False



def expandString(StartString=''):
    if lastCharMakesSolvable(StartString):
        return StartString + '0'
    return StartString + '1'

n=1
BaseStr=""
lastCount=0
while n<1000000:
    BaseStr=expandString(BaseStr)
    count=BaseStr.count('1')
    if count != lastCount:
        print(len(BaseStr), count)
    lastCount=count
    n=n+1

(A mia difesa, sono ancora nella fase di comprensione "impara il pitone")

Inoltre, uscita potenzialmente utile dall'avida costruzione di stringhe, c'è un salto piuttosto consistente dopo aver colpito una potenza di 2 nel numero di 1 ... che non ero disposto ad aspettare in giro per assistere al colpo del 2096.

strlength   # of 1's
    1    1
    2    2
    4    3
    5    4
   10    5
   14    8
   28    9
   41    16
   82    17
  122    32
  244    33
  365    64
  730    65
 1094    128
 2188    129
 3281    256
 6562    257
 9842    512
19684    513
29525    1024

0

Proverò a presentare un approccio matematico. Questo è più un inizio che una fine, quindi qualsiasi aiuto, commento o persino contraddizione - sarà profondamente apprezzato. Tuttavia, se questo approccio è dimostrato, l'algoritmo è una ricerca diretta nella stringa.

  1. Dato un numero fisso di spazi ke una stringa S, la ricerca di una terzina k-spaziata richiede O(n)- Testiamo semplicemente ogni 0<=i<=(n-2k)if S[i]==S[i+k]==S[i+2k]. Il test prende O(1)e lo facciamo n-kvolte dove kè una costante, quindi ci vuole O(n-k)=O(n).

  2. Supponiamo che ci sia una proporzione inversa tra il numero di 1"s" e gli spazi massimi che dobbiamo cercare. Cioè, se ce ne sono molti 1, ci deve essere una tripletta e deve essere abbastanza densa; Se ce ne sono solo pochi 1, la tripletta (se presente) può essere piuttosto scarsa. In altre parole, posso dimostrare che se ne ho abbastanza 1, tale tripletta deve esistere - e più 1ne ho, deve essere trovata una tripletta più densa. Ciò può essere spiegato dal principio di Pigeonhole : spero di approfondire più avanti.

  3. Supponiamo che abbia un limite superiore ksul possibile numero di spazi che devo cercare. Ora, per ogni 1situato in S[i]abbiamo bisogno di verificare la presenza di 1in S[i-1]e S[i+1], S[i-2]e S[i+2], ... S[i-k]e S[i+k]. Questo vale O((k^2-k)/2)=O(k^2)per ogni 1in S- a causa della formula di somma della serie di Gauss . Nota che questo differisce dalla sezione 1 - Sto avendo kun limite superiore per il numero di spazi, non come uno spazio costante.

Dobbiamo provare O(n*log(n)). Cioè, dobbiamo dimostrare che k*(number of 1's)è proporzionale a log(n).

Se riusciamo a farlo, l'algoritmo è banale - per ognuno 1nel Scui indice è i, basta cercare 1"da ogni lato fino alla distanza" k. Se due sono stati trovati nella stessa distanza, tornare ie k. Ancora una volta, la parte difficile sarebbe trovare ke dimostrare la correttezza.

Gradirei molto i tuoi commenti qui - Ho cercato di trovare la relazione tra ke il numero di 1'' sulla mia lavagna, finora senza successo.


0

Assunzione:

Semplicemente sbagliato, parlando del numero di registro (n) del limite superiore di quelli

MODIFICARE:

Ora ho scoperto che usando i numeri Cantor (se corretto), la densità sul set è (2/3) ^ Log_3 (n) (che strana funzione) e sono d'accordo, la densità log (n) / n è troppo forte.

Se questo è il limite superiore, esiste un algoritmo che risolve questo problema in almeno O (n * (3/2) ^ (log (n) / log (3))) complessità temporale e O ((3/2) ^ ( log (n) / log (3))) complessità dello spazio. (controlla la risposta di Justice per algoritmo)

Questo è ancora di gran lunga migliore di O (n ^ 2)

Questa funzione ((3/2) ^ (log (n) / log (3))) sembra davvero n * log (n) a prima vista.

Come ho ottenuto questa formula?

Visualizzazione del numero di Cantors su stringa.
Supponi che la lunghezza della stringa sia 3 ^ p == n
Ad ogni passaggio della generazione della stringa Cantor mantieni i 2/3 del numero precedente di quelli. Applicare questo p volte.

Ciò significa che (n * ((2/3) ^ p)) -> (((3 ^ p)) * ((2/3) ^ p)) quelli rimanenti e dopo la semplificazione 2 ^ p. Ciò significa 2 ^ p in 3 ^ p stringa -> (3/2) ^ p uni. Sostituisci p = log (n) / log (3) e ottieni
((3/2) ^ (log (n) / log (3)))


Falso: l'insieme di Cantor ha densità n ^ log_3 (2).
sdcvvc,

0

Che ne dici di una semplice soluzione O (n), con spazio O (n ^ 2)? (Utilizza il presupposto che tutti gli operatori bit a bit lavorano in O (1).)

L'algoritmo funziona sostanzialmente in quattro fasi:

Fase 1: per ogni bit del tuo numero originale, scopri quanto sono lontani quelli, ma considera solo una direzione. (Ho considerato tutti i bit nella direzione del bit meno significativo.)

Fase 2: invertire l'ordine dei bit nell'input;

Fase 3: rieseguire il passaggio 1 sull'ingresso invertito.

Fase 4: confrontare i risultati della fase 1 e della fase 3. Se tutti i bit sono equidistanti tra E sopra e sotto dobbiamo avere un colpo.

Tieni presente che nessun passaggio dell'algoritmo sopra richiede più tempo di O (n). ^ _ ^

Come ulteriore vantaggio, questo algoritmo troverà TUTTI quelli ugualmente distanziati da OGNI numero. Ad esempio, se ottieni un risultato di "0x0005", ce ne sono altri equidistanti a ENTRAMBE 1 e 3 unità

Non ho davvero provato a ottimizzare il codice qui sotto, ma è un codice C # compilabile che sembra funzionare.

using System;

namespace ThreeNumbers
{
    class Program
    {
        const int uint32Length = 32;

        static void Main(string[] args)
        {
            Console.Write("Please enter your integer: ");
            uint input = UInt32.Parse(Console.ReadLine());

            uint[] distancesLower = Distances(input);
            uint[] distancesHigher = Distances(Reverse(input));

            PrintHits(input, distancesLower, distancesHigher);
        }

        /// <summary>
        /// Returns an array showing how far the ones away from each bit in the input.  Only 
        /// considers ones at lower signifcant bits.  Index 0 represents the least significant bit 
        /// in the input.  Index 1 represents the second least significant bit in the input and so 
        /// on.  If a one is 3 away from the bit in question, then the third least significant bit 
        /// of the value will be sit.
        /// 
        /// As programed this algorithm needs: O(n) time, and O(n*log(n)) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        public static uint[] Distances(uint input)
        {
            uint[] distanceToOnes = new uint[uint32Length];
            uint result = 0;

            //Sets how far each bit is from other ones. Going in the direction of LSB to MSB
            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                distanceToOnes[arrayIndex] = result;
                result <<= 1;

                if ((input & bitIndex) != 0)
                {
                    result |= 1;
                }
            }

            return distanceToOnes;
        }

        /// <summary>
        /// Reverses the bits in the input.
        /// 
        /// As programmed this algorithm needs O(n) time and O(n) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static uint Reverse(uint input)
        {
            uint reversedInput = 0;
            for (uint bitIndex = 1; bitIndex != 0; bitIndex <<= 1)
            {
                reversedInput <<= 1;
                reversedInput |= (uint)((input & bitIndex) != 0 ? 1 : 0);
            }

            return reversedInput;
        }

        /// <summary>
        /// Goes through each bit in the input, to check if there are any bits equally far away in 
        /// the distancesLower and distancesHigher
        /// </summary>
        public static void PrintHits(uint input, uint[] distancesLower, uint[] distancesHigher)
        {
            const int offset = uint32Length - 1;

            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                //hits checks if any bits are equally spaced away from our current value
                bool isBitSet = (input & bitIndex) != 0;
                uint hits = distancesLower[arrayIndex] & distancesHigher[offset - arrayIndex];

                if (isBitSet && (hits != 0))
                {
                    Console.WriteLine(String.Format("The {0}-th LSB has hits 0x{1:x4} away", arrayIndex + 1, hits));
                }
            }
        }
    }
}

Qualcuno probabilmente commenterà che per qualsiasi numero sufficientemente grande, non è possibile eseguire operazioni bit a bit in O (1). Avresti ragione. Tuttavia, suppongo che ogni soluzione che usa addizione, sottrazione, moltiplicazione o divisione (che non può essere fatta spostando) avrebbe anche quel problema.


0

Di seguito è una soluzione. Potrebbero esserci alcuni piccoli errori qua e là, ma l'idea è valida.

Modifica: non è n * log (n)

CODICE PSEUDO:

foreach character in the string
  if the character equals 1 {         
     if length cache > 0 { //we can skip the first one
        foreach location in the cache { //last in first out kind of order
           if ((currentlocation + (currentlocation - location)) < length string)
              if (string[(currentlocation + (currentlocation - location))] equals 1)
                 return found evenly spaced string
           else
              break;
        }
     }
     remember the location of this character in a some sort of cache.
  }

return didn't find evenly spaced string

Codice C #:

public static Boolean FindThreeEvenlySpacedOnes(String str) {
    List<int> cache = new List<int>();

    for (var x = 0; x < str.Length; x++) {
        if (str[x] == '1') {
            if (cache.Count > 0) {
                for (var i = cache.Count - 1; i > 0; i--) {
                    if ((x + (x - cache[i])) >= str.Length)
                        break;

                    if (str[(x + (x - cache[i]))] == '1')
                        return true;                            
                }
            }
            cache.Add(x);                    
        }
    }

    return false;
}

Come funziona:

iteration 1:
x
|
101101001
// the location of this 1 is stored in the cache

iteration 2:
 x
 | 
101101001

iteration 3:
a x b 
| | | 
101101001
//we retrieve location a out of the cache and then based on a 
//we calculate b and check if te string contains a 1 on location b

//and of course we store x in the cache because it's a 1

iteration 4:
  axb  
  |||  
101101001

a  x  b  
|  |  |  
101101001


iteration 5:
    x  
    |  
101101001

iteration 6:
   a x b 
   | | | 
101101001

  a  x  b 
  |  |  | 
101101001
//return found evenly spaced string

1
Il tuo algoritmo funziona, ma devi dimostrare che è inferiore a O (n ^ 2). L'analisi banale ti porta a O (n ^ 2). Per ogni 1, vai su tutti gli 1 precedenti. Rendere la funzione di complessità 1 + 2 + 3 + ... + (k / 2-1) = O (k ^ 2) [dove k è il numero di 1s].
Anna,

Ho controllato e in effetti lo scenario peggiore senza soluzione è maggiore di O (n * log (n))
Niek H.

0

Ovviamente dobbiamo almeno controllare gruppi di terzine allo stesso tempo, quindi dobbiamo comprimere i controlli in qualche modo. Ho un algoritmo candidato, ma l'analisi della complessità temporale è oltre la mia soglia di capacità *.

Costruisci un albero in cui ogni nodo ha tre figli e ogni nodo contiene il numero totale di 1 alle sue foglie. Costruisci anche un elenco collegato sopra gli 1. Assegna a ciascun nodo un costo consentito proporzionale all'intervallo coperto. Finché il tempo che passiamo in ciascun nodo è nel budget, avremo un algoritmo O (n lg n).

-

Inizia dalla radice. Se il quadrato del numero totale di 1 sotto è inferiore al suo costo consentito, applica l'algoritmo ingenuo. Altrimenti si affidano ai suoi figli.

Ora siamo tornati nel budget o sappiamo che non ci sono terzine valide interamente contenute in uno dei bambini. Pertanto, dobbiamo controllare le terzine inter-nodo.

Ora le cose diventano incredibilmente confuse. Vogliamo essenzialmente fare affidamento sui potenziali gruppi di bambini, limitando al contempo la portata. Non appena l'intervallo è abbastanza limitato da far sì che l'algoritmo ingenuo venga eseguito in budget, lo fai. Divertiti a implementarlo, perché ti garantisco che sarà noioso. Ci sono una dozzina di casi.

-

Il motivo per cui penso che l'algoritmo funzionerà è perché le sequenze senza terzine valide sembrano alternarsi tra gruppi di 1 e molti 0. Divide efficacemente lo spazio di ricerca vicino e l'albero emula tale divisione.

Il tempo di esecuzione dell'algoritmo non è affatto ovvio. Si basa sulle proprietà non banali della sequenza. Se gli 1 sono davvero scarsi, l'algoritmo ingenuo funzionerà con budget limitato. Se gli 1 sono densi, allora dovrebbe essere trovata subito una corrispondenza. Ma se la densità è "giusta" (es. Vicino a ~ n ^ 0,63, che puoi ottenere impostando tutti i bit in posizioni senza una cifra "2" nella base 3), non so se funzionerà. Dovresti dimostrare che l'effetto di scissione è abbastanza forte.


0

Nessuna risposta teorica qui, ma ho scritto un rapido programma Java per esplorare il comportamento del tempo di esecuzione in funzione di k e n, dove n è la lunghezza totale dei bit e k è il numero di 1. Sono con alcuni dei rispondenti che affermano che l'algoritmo "normale" che controlla tutte le coppie di posizioni dei bit e cerca il 3o bit, anche se richiederebbe O (k ^ 2) nel peggiore dei casi, in realtà perché il caso peggiore necessita di stringhe di bit sparse, è O (n ln n).

Comunque ecco il programma, di seguito. È un programma in stile Monte-Carlo che esegue un gran numero di prove NTRIALS per n costante e genera casualmente bitset per un intervallo di valori k usando processi di Bernoulli con una densità limitata tra limiti che possono essere specificati e registra il tempo di esecuzione di trovare o non riuscire a trovare una tripletta di quelli equidistanti, tempo misurato in passi NON in tempo CPU. L'ho eseguito per n = 64, 256, 1024, 4096, 16384 * (ancora in esecuzione), prima un test eseguito con 500000 prove per vedere quali valori k richiedono il tempo di esecuzione più lungo, quindi un altro test con 5000000 prove con quelle ristrette- focus sulla densità per vedere che aspetto hanno questi valori. I tempi di esecuzione più lunghi si verificano con densità molto scarsa (ad es. Per n = 4096 i picchi dei tempi di esecuzione sono nell'intervallo k = 16-64, con un picco lieve per l'autonomia media a 4212 passi @ k = 31, tempo di esecuzione massimo raggiunto a 5101 passi @ k = 58). Sembra che ci vorranno valori estremamente grandi di N per il passo O (k ^ 2) nel caso peggiore per diventare più grande del passo O (n) in cui si esegue la scansione della stringa di bit per trovare gli indici di posizione di 1.

package com.example.math;

import java.io.PrintStream;
import java.util.BitSet;
import java.util.Random;

public class EvenlySpacedOnesTest {
    static public class StatisticalSummary
    {
        private int n=0;
        private double min=Double.POSITIVE_INFINITY;
        private double max=Double.NEGATIVE_INFINITY;
        private double mean=0;
        private double S=0;

        public StatisticalSummary() {}
        public void add(double x) {
            min = Math.min(min, x);
            max = Math.max(max, x);
            ++n;
            double newMean = mean + (x-mean)/n;
            S += (x-newMean)*(x-mean);
            // this algorithm for mean,std dev based on Knuth TAOCP vol 2
            mean = newMean;
        }
        public double getMax() { return (n>0)?max:Double.NaN; }
        public double getMin() { return (n>0)?min:Double.NaN; }
        public int getCount() { return n; }
        public double getMean() { return (n>0)?mean:Double.NaN; }
        public double getStdDev() { return (n>0)?Math.sqrt(S/n):Double.NaN; } 
        // some may quibble and use n-1 for sample std dev vs population std dev    
        public static void printOut(PrintStream ps, StatisticalSummary[] statistics) {
            for (int i = 0; i < statistics.length; ++i)
            {
                StatisticalSummary summary = statistics[i];
                ps.printf("%d\t%d\t%.0f\t%.0f\t%.5f\t%.5f\n",
                        i,
                        summary.getCount(),
                        summary.getMin(),
                        summary.getMax(),
                        summary.getMean(),
                        summary.getStdDev());
            }
        }
    }

    public interface RandomBernoulliProcess // see http://en.wikipedia.org/wiki/Bernoulli_process
    {
        public void setProbability(double d);
        public boolean getNextBoolean();
    }

    static public class Bernoulli implements RandomBernoulliProcess
    {
        final private Random r = new Random();
        private double p = 0.5;
        public boolean getNextBoolean() { return r.nextDouble() < p; }
        public void setProbability(double d) { p = d; }
    }   
    static public class TestResult {
        final public int k;
        final public int nsteps;
        public TestResult(int k, int nsteps) { this.k=k; this.nsteps=nsteps; } 
    }

    ////////////
    final private int n;
    final private int ntrials;
    final private double pmin;
    final private double pmax;
    final private Random random = new Random();
    final private Bernoulli bernoulli = new Bernoulli();
    final private BitSet bits;
    public EvenlySpacedOnesTest(int n, int ntrials, double pmin, double pmax) {
        this.n=n; this.ntrials=ntrials; this.pmin=pmin; this.pmax=pmax;
        this.bits = new BitSet(n);
    }

    /*
     * generate random bit string
     */
    private int generateBits()
    {
        int k = 0; // # of 1's
        for (int i = 0; i < n; ++i)
        {
            boolean b = bernoulli.getNextBoolean();
            this.bits.set(i, b);
            if (b) ++k;
        }
        return k;
    }

    private int findEvenlySpacedOnes(int k, int[] pos) 
    {
        int[] bitPosition = new int[k];
        for (int i = 0, j = 0; i < n; ++i)
        {
            if (this.bits.get(i))
            {
                bitPosition[j++] = i;
            }
        }
        int nsteps = n; // first, it takes N operations to find the bit positions.
        boolean found = false;
        if (k >= 3) // don't bother doing anything if there are less than 3 ones. :(
        {       
            int lastBitSetPosition = bitPosition[k-1];
            for (int j1 = 0; !found && j1 < k; ++j1)
            {
                pos[0] = bitPosition[j1];
                for (int j2 = j1+1; !found && j2 < k; ++j2)
                {
                    pos[1] = bitPosition[j2];

                    ++nsteps;
                    pos[2] = 2*pos[1]-pos[0];
                    // calculate 3rd bit index that might be set;
                    // the other two indices point to bits that are set
                    if (pos[2] > lastBitSetPosition)
                        break;
                    // loop inner loop until we go out of bounds

                    found = this.bits.get(pos[2]);
                    // we're done if we find a third 1!
                }
            }
        }
        if (!found)
            pos[0]=-1;
        return nsteps;
    }

    /*
     * run an algorithm that finds evenly spaced ones and returns # of steps.
     */
    public TestResult run()
    {
        bernoulli.setProbability(pmin + (pmax-pmin)*random.nextDouble());
        // probability of bernoulli process is randomly distributed between pmin and pmax

        // generate bit string.
        int k = generateBits();
        int[] pos = new int[3];
        int nsteps = findEvenlySpacedOnes(k, pos);
        return new TestResult(k, nsteps); 
    }

    public static void main(String[] args)
    {
        int n;
        int ntrials;
        double pmin = 0, pmax = 1;
        try {
            n = Integer.parseInt(args[0]);
            ntrials = Integer.parseInt(args[1]);
            if (args.length >= 3)
                pmin = Double.parseDouble(args[2]);
            if (args.length >= 4)
                pmax = Double.parseDouble(args[3]);
        }
        catch (Exception e)
        {
            System.out.println("usage: EvenlySpacedOnesTest N NTRIALS [pmin [pmax]]");
            System.exit(0);
            return; // make the compiler happy
        }

        final StatisticalSummary[] statistics;
        statistics=new StatisticalSummary[n+1];
        for (int i = 0; i <= n; ++i)
        {
            statistics[i] = new StatisticalSummary();
        }

        EvenlySpacedOnesTest test = new EvenlySpacedOnesTest(n, ntrials, pmin, pmax);
        int printInterval=100000;
        int nextPrint = printInterval;
        for (int i = 0; i < ntrials; ++i)
        {
            TestResult result = test.run();
            statistics[result.k].add(result.nsteps);
            if (i == nextPrint)
            {
                System.err.println(i);
                nextPrint += printInterval;
            }
        }
        StatisticalSummary.printOut(System.out, statistics);
    }
}

0
# <algorithm>
def contains_evenly_spaced?(input)
  return false if input.size < 3
  one_indices = []
  input.each_with_index do |digit, index|
    next if digit == 0
    one_indices << index
  end
  return false if one_indices.size < 3
  previous_indexes = []
  one_indices.each do |index|
    if !previous_indexes.empty?
      previous_indexes.each do |previous_index|
        multiple = index - previous_index
        success_index = index + multiple
        return true if input[success_index] == 1
      end
    end
    previous_indexes << index
  end
  return false
end
# </algorithm>

def parse_input(input)
  input.chars.map { |c| c.to_i }
end

Ho problemi con gli scenari peggiori con milioni di cifre. Il fuzzing da /dev/urandomessenzialmente ti dà O (n), ma so che il caso peggiore è peggio di così. Non riesco proprio a dire quanto peggio. Per i piccoli n, è banale trovare input in giro 3*n*log(n), ma è sorprendentemente difficile distinguerli da un altro ordine di crescita per questo particolare problema.

Qualcuno che stava lavorando su input nel caso peggiore può generare una stringa con lunghezza maggiore di dire, centomila?


Come ho sottolineato nella mia risposta, è facile generare stringhe cattive (anche se non nel caso peggiore) di qualsiasi numero di cifre: metti 1s esattamente in quelle posizioni p che non contengono alcun "1" nella loro rappresentazione ternaria (cioè posizioni 2, 6, 8, 18, 20, 24, 26, 54, 56, 60 ...: vedere le formule su research.att.com/~njas/sequences/…). Per 3 ^ 13 ≈ 1 milione, questo ha 2 ^ 13 ≈ 8000 1s. Il tempo di esecuzione su tali stringhe sarà ≈ n ^ (1.26) - che potrebbe essere ancora difficile distinguere da O (n log n) per un numero così piccolo n. Provalo e vedi.
ShreevatsaR,


-3

Potrebbe essere una soluzione? I ', non sono sicuro che sia O (nlogn) ma secondo me è meglio di O (n²) perché l'unico modo per non trovare una tripla sarebbe una distribuzione in numeri primi.

C'è spazio per miglioramenti, il secondo trovato 1 potrebbe essere il prossimo primo 1. Inoltre nessun controllo degli errori.

#include <iostream>

#include <string>

int findIt(std::string toCheck) {
    for (int i=0; i<toCheck.length(); i++) {
        if (toCheck[i]=='1') {
            std::cout << i << ": " << toCheck[i];
            for (int j = i+1; j<toCheck.length(); j++) {
                if (toCheck[j]=='1' && toCheck[(i+2*(j-i))] == '1') {
                    std::cout << ", " << j << ":" << toCheck[j] << ", " << (i+2*(j-i)) << ":" << toCheck[(i+2*(j-i))] << "    found" << std::endl;
                    return 0;
                }
            }
        }
    }
    return -1;
}

int main (int agrc, char* args[]) {
    std::string toCheck("1001011");
    findIt(toCheck);
    std::cin.get();
    return 0;
}

1
Tecnicamente questo è O (n ^ 2). In media il ciclo interno ripeterà oltre la metà di n ogni volta che viene eseguito. Quindi potrebbe essere scritto come O (n * (n / 2)), e questo può essere semplificato in O (n ^ 2)
Robert Parker,

Hm, sembra che tu abbia ragione. Questo non è un problema semplice, solo per trovare tutti i 1 prende O (n), non c'è molto spazio per ulteriori ricerche / confronti con la complessità di O (logn).
DaClown,

-3

Penso che questo algoritmo abbia complessità O (n log n) (C ++, DevStudio 2k5). Ora, non conosco i dettagli su come analizzare un algoritmo per determinarne la complessità, quindi ho aggiunto alcune metriche per raccogliere informazioni sul codice. Il codice conta il numero di test eseguiti sulla sequenza di 1 e 0 per ogni dato input (si spera, non ho fatto palle dell'algoritmo). Possiamo confrontare il numero effettivo di test con il valore O e vedere se c'è una correlazione.

#include <iostream>
using namespace std;

bool HasEvenBits (string &sequence, int &num_compares)
{
  bool
    has_even_bits = false;

  num_compares = 0;

  for (unsigned i = 1 ; i <= (sequence.length () - 1) / 2 ; ++i)
  {
    for (unsigned j = 0 ; j < sequence.length () - 2 * i ; ++j)
    {
      ++num_compares;
      if (sequence [j] == '1' && sequence [j + i] == '1' && sequence [j + i * 2] == '1')
      {
        has_even_bits = true;
        // we could 'break' here, but I want to know the worst case scenario so keep going to the end
      }
    }
  }

  return has_even_bits;
}

int main ()
{
  int
    count;

  string
    input = "111";

  for (int i = 3 ; i < 32 ; ++i)
  {
    HasEvenBits (input, count);
    cout << i << ", " << count << endl;
    input += "0";
  }
}

Questo programma genera il numero di test per ogni lunghezza di stringa fino a 32 caratteri. Ecco i risultati:

 n  Tests  n log (n)
=====================
 3     1     1.43
 4     2     2.41
 5     4     3.49
 6     6     4.67
 7     9     5.92
 8    12     7.22
 9    16     8.59
10    20    10.00
11    25    11.46
12    30    12.95
13    36    14.48
14    42    16.05
15    49    17.64
16    56    19.27
17    64    20.92
18    72    22.59
19    81    24.30
20    90    26.02
21   100    27.77
22   110    29.53
23   121    31.32
24   132    33.13
25   144    34.95
26   156    36.79
27   169    38.65
28   182    40.52
29   196    42.41
30   210    44.31
31   225    46.23

Ho aggiunto anche i valori 'n log n'. Tracciali utilizzando il tuo strumento grafico preferito per vedere una correlazione tra i due risultati. Questa analisi si estende a tutti i valori di n? Non lo so.


Non è una correlazione perfetta, sono d'accordo. Tuttavia, la curva è più vicina a n log n che a n ^ 2.
Skizz,

3
Prova a pompare la dimensione dell'input fino a un milione o più. A piccoli input la curva appare spesso simile alle curve degli algoritmi che sono ovviamente migliori quando le dimensioni degli input vengono aumentate.
Nick Larsen,

Un doppio per loop con quello interno delimitato da quello esterno crea una forma triangolare, che è ancora O (n ^ 2) in complessità. Pensa a tutti (i, j) tali che i in [0, n] e j in [0, n-2 * i], hai un triangolo e l'area di un triangolo ha una tendenza quadratica.
Matthieu M.,

Per essere precisi, Test = (n ^ 2-2n) / 4 per pari n; ovviamente quadratico.
Nonno,
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.