O (nlogn) -Algorithmus - Finden Sie drei gleichmäßig verteilte innerhalb einer Binärzeichenfolge


173

Ich hatte diese Frage gestern bei einem Algorithmus-Test und kann die Antwort nicht herausfinden. Es macht mich absolut verrückt, weil es ungefähr 40 Punkte wert war. Ich denke, dass der Großteil der Klasse es nicht richtig gelöst hat, weil ich in den letzten 24 Stunden keine Lösung gefunden habe.

Suchen Sie bei einer beliebigen binären Zeichenfolge mit der Länge n drei gleichmäßig verteilte Zeichenfolgen innerhalb der Zeichenfolge, falls vorhanden. Schreiben Sie einen Algorithmus, der dies in O (n * log (n)) Zeit löst.

Zeichenfolgen wie diese haben also drei, die "gleichmäßig verteilt" sind: 11100000, 0100100100

Bearbeiten: Es ist eine Zufallszahl, daher sollte es für jede Zahl funktionieren können. Die Beispiele, die ich gab, sollten die Eigenschaft "gleichmäßig verteilt" veranschaulichen. 1001011 ist also eine gültige Nummer. Mit 1, 4 und 7 sind diejenigen, die gleichmäßig verteilt sind.


4
Ist folgendes möglich: 10011010000? Es hat drei Einsen (erste, zweite, vierte), die gleichmäßig verteilt sind, aber es gibt auch zusätzliche Einsen.
Anna

5
Robert, du musst deinen Professor dazu bringen, dir die Antwort darauf zu geben und sie hier zu posten. Dieses Problem treibt mich die Wand hoch. Ich kann herausfinden, wie es in n ^ 2 geht, aber nicht in n * log (n).
James McMahon

3
Hmm, ich habe lange versucht, das auch herauszufinden, habe aber noch keine gute Antwort gefunden. Vielleicht haben Sie die Frage falsch verstanden? Wenn zum Beispiel die Frage gestellt wird, finden Sie einen Algorithmus, der in O (n log n) ausgeführt wird und die Position einer gleichmäßig beabstandeten Folge von Abständen k in einer viel größeren Folge bestimmt. Dies könnte leicht unter Verwendung der schnellen Fourier-Transformation erfolgen.
ldog

2
Wenn Ihr Professor eine Lösung gibt, senden Sie diese bitte als Antwort.
ldog

5
In Anbetracht der Tatsache, dass Klaus Roth 1958 eine Feldmedaille erhielt, um (unter anderem) zu beweisen, dass es für jede Dichte d> 0 eine natürliche Zahl N gibt, so dass jede Teilmenge von {1, ..., N} mit mindestens d * N Elemente enthalten eine arithmetische Folge der Länge 3, ich bin nicht überrascht, dass bisher noch niemand einen überzeugenden Algorithmus für das Problem gefunden hat. Siehe auch en.wikipedia.org/wiki/Szemer%C3%A9di%27s_theorem
jp

Antworten:


128

Schließlich! Nach den Hinweisen in der Antwort von sdcvvc haben wir es: den O (n log n) -Algorithmus für das Problem! Es ist auch einfach, nachdem Sie es verstanden haben. Diejenigen, die FFT vermuteten, hatten Recht.

Das Problem: Wir erhalten eine binäre Zeichenfolge mit Sder Länge n und möchten drei gleichmäßig verteilte Einsen darin finden. Zum Beispiel Skann sein 110110010, wobei n = 9 ist. Es hat einen gleichmäßigen Abstand von 1s an den Positionen 2, 5 und 8.

  1. Scannen Sie von Slinks nach rechts und erstellen Sie eine Liste Lmit Positionen von 1. Für die S=110110010obigen Angaben haben wir die Liste L = [1, 2, 4, 5, 8]. Dieser Schritt ist O (n). Das Problem ist nun eine finden arithmetische Progression der Länge 3 in L, das heißt zu finden deutliche a, b, c in Lderart , daß ba = cb oder äquivalent a + c = 2b . Für das obige Beispiel wollen wir den Verlauf finden (2, 5, 8).

  2. Machen Sie ein Polynom p mit Termen x k für jedes k in L. Für das obige Beispiel machen wir das Polynom p (x) = (x + x 2 + x 4 + x 5 + x 8 ) . Dieser Schritt ist O (n).

  3. Finden Sie das Polynom q= p 2 mit der Fast Fourier Transform . Für das obige Beispiel erhalten wir das Polynom 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 . Dieser Schritt ist O (n log n).

  4. Ignorieren Sie alle Begriffe außer denen, die x 2k für einige k in entsprechen L. Für das obige Beispiel erhalten wir die Terme x 16 , 3x 10 , x 8 , x 4 , x 2 . Dieser Schritt ist O (n), wenn Sie dies überhaupt tun möchten.

Hier ist der entscheidende Punkt: der Koeffizient jeden x 2b für b in List genau die Anzahl von Paaren (a, c) in Lderart , daß a + c = 2b . [CLRS, Bsp. 30.1-7] Ein solches Paar ist immer (b, b) (der Koeffizient ist also mindestens 1), aber wenn es ein anderes Paar (a, c) gibt , dann ist der Koeffizient mindestens 3 von (a, c) ) und (c, a) . Für das obige Beispiel haben wir den Koeffizienten von x 10 genau wegen des AP (2,5,8) auf 3. (Diese Koeffizienten x 2bwird aus den oben genannten Gründen immer ungerade Zahlen sein. Und alle anderen Koeffizienten in q sind immer gerade.)

Der Algorithmus besteht also darin, die Koeffizienten dieser Terme x 2b zu betrachten und festzustellen , ob einer von ihnen größer als 1 ist. Wenn es keine gibt, gibt es keine gleichmäßig verteilten 1s. Wenn es ist ein b in , Lfür die der Koeffizient der x 2b größer als 1 ist, dann wissen wir , dass es einige Paare (a, c) - andere als (b, b) - für welches ein + c = 2b . Um das tatsächliche Paar zu finden, versuchen wir einfach jedes a in L(das entsprechende c wäre 2b-a ) und prüfen, ob es an Position 2b-a in eine 1 gibt S. Dieser Schritt ist O (n).

Das war's Leute.


Man könnte fragen: Müssen wir FFT verwenden? Viele Antworten, wie Beta des , Fly-By-Wire ist , und rsp ist , deuten darauf hin , dass der Ansatz , dass die Kontrollen jedes Paar von 1s und sieht , wenn es eine 1 an der „dritte“ Position ist, kann Arbeit in O (n log n), auf der Grundlage der Intuition Wenn es zu viele Einsen gibt, finden wir leicht ein Tripel, und wenn es zu wenige Einsen gibt, dauert es wenig Zeit, alle Paare zu überprüfen. Leider, während diese Intuition richtig und der einfache Ansatz ist ist besser als O (n 2 ), ist es nicht wesentlich besser. Wie in der Antwort von sdcvvc können wir die "Cantor-ähnliche Menge" von Strings der Länge n = 3 k nehmenmit 1s an den Positionen, deren ternäre Darstellung nur 0s und 2s (keine 1s) enthält. Eine solche Zeichenfolge enthält 2 k = n (log 2) / (log 3) ≈ n 0,63 Einsen und keine gleichmäßig verteilten 1s. Die Überprüfung aller Paare würde also in der Größenordnung des Quadrats der Anzahl der 1s liegen: das ist 4 k ≈ n 1,26, was leider asymptotisch viel größer ist als (n log n). In der Tat ist der schlimmste Fall noch schlimmer: Leo Moser konstruierte 1953 (effektiv) solche Strings, die n 1-c / √ (log n) 1s enthalten, aber keine gleichmäßig verteilten 1s, was bedeutet, dass auf solchen Strings die einfachen Ansatz würde Θ (n 2-2c / √ (log n) ) nehmen- überraschenderweise nur ein kleines bisschen besser als Θ (n 2 ) !


Ungefähr die maximale Anzahl von 1s in einer Zeichenfolge mit der Länge n ohne 3 gleichmäßig verteilte (was wir oben gesehen haben, war mindestens n 0,63 aus der einfachen Cantor-ähnlichen Konstruktion und mindestens n 1-c / √ (log n) mit Mosers Konstruktion) - das ist OEIS A003002 . Sie kann auch direkt aus OEIS A065825 als k berechnet werden, so dass A065825 (k) ≤ n <A065825 (k + 1) ist. Ich habe ein Programm geschrieben, um diese zu finden, und es stellt sich heraus, dass der Greedy-Algorithmus nicht die längste solche Zeichenfolge liefert . Zum Beispiel können wir für n = 9 5 1s (110100011) erhalten, aber der Gierige gibt nur 4 (110110000) für n= 26 wir können 11 1s erhalten (11001010001000010110001101), aber der Gierige gibt nur 8 (1101100001101100000000000000), und für n = 74 können wir 22 1s erhalten (11000010110001000001011010001000000000000000010010100001000010000100 Sie stimmen jedoch an einigen Stellen bis 50 überein (z. B. alle 38 bis 50). Wie aus den OEIS-Referenzen hervorgeht, scheint Jaroslaw Wroblewski an dieser Frage interessiert zu sein, und er unterhält eine Website zu diesen nicht gemittelten Sets . Die genauen Zahlen sind nur bis 194 bekannt.


27
Sehr schön. Beeindruckend. Es scheint ein bisschen viel zu erwarten, dass sich jemand dies in einem Test einfallen lässt.
Hughdbrown

4
Nun, Schritt 1, bei dem das Problem in die Suche nach einem AP übersetzt wird, ist unkompliziert. Schritt 3, dass Polynome in O (n log n) Zeit multipliziert werden können, ist nur eine Tatsache. Der eigentliche Trick und was das Problem schwierig macht, ist die Idee, 11011 als Polynom mit Koeffizienten [1,1,0,1,1] usw. zu betrachten. Dies ist eine clevere und oft nützliche Idee, die alles in allem tut Weg zurück zu Euler. [Eine moderne Darstellung finden Sie in Wilfs großartigem Buch "Generating Functionology " : math.upenn.edu/~wilf/DownldGF.html ] Es hängt also davon ab, ob die Schüler in jüngster Zeit Generierungsfunktionen ausgesetzt waren oder nicht. :-)
ShreevatsaR

2
Entschuldigung, meine Berechnung ist völlig falsch. Es sollte 110110010 ^ 2 = 12124214302200100 sein. Aber die Idee steht. Beachten Sie einfach die Position des 3.
Guillermo Phillips

11
Sehr beeindruckend. Es ist wirklich cool zu sehen, wie dieser Thread / diese Frage zusammenkommt und eine Lösung findet. Ich begann zu denken, dass es nicht möglich war. Auch dieser Professor ist böse.
KingNestor

1
@RexE: Wenn p vom Grad n-1 ist (n Terme hat), ist q = p ^ 2 vom Grad 2n-2 (hat höchstens 2n-1 Terme). Wie bist du zu n ^ 2 gekommen? (Auch das Multiplizieren von zwei Polynomen des Grades n in O (n log n) mit der FFT ist eine ziemlich normale Operation; bitte klicken Sie auf den Link in der Antwort oder
lesen

35

Ihr Problem wird in diesem Artikel (1999) als DURCHSCHNITTLICH bezeichnet :

Ein Problem ist 3SUM-schwer, wenn es eine subquadratische Reduktion gegenüber dem Problem 3SUM gibt: Gibt es bei einer Menge A von n ganzen Zahlen Elemente a, b, c in A, so dass a + b + c = 0 ist? Es ist nicht bekannt, ob AVERAGE 3SUM-hard ist. Es gibt jedoch eine einfache lineare Zeitreduzierung von AVERAGE auf 3SUM, deren Beschreibung wir weglassen.

Wikipedia :

Wenn die ganzen Zahlen im Bereich [−u ... u] liegen, kann 3SUM in der Zeit O (n + u lg u) gelöst werden, indem S als Bitvektor dargestellt und eine Faltung unter Verwendung von FFT durchgeführt wird.

Dies ist genug, um Ihr Problem zu lösen :).

Was sehr wichtig ist, ist, dass O (n log n) Komplexität in Bezug auf die Anzahl der Nullen und Einsen ist, nicht die Anzahl der Einsen (die als Array angegeben werden könnten, wie [1,5,9,15]). Es ist schwierig zu überprüfen, ob eine Menge eine arithmetische Folge hat, ausgedrückt als Anzahl von Einsen, und laut dieser Veröffentlichung ist ab 1999 kein schnellerer Algorithmus als O (n 2 ) bekannt, und es wird vermutet, dass er nicht existiert. Jeder, der dies nicht berücksichtigt, versucht, ein offenes Problem zu lösen.

Andere interessante Informationen, meistens irrelevant:

Untergrenze:

Eine einfache Untergrenze ist eine Cantor-ähnliche Menge (Zahlen 1..3 ^ n-1, die in ihrer ternären Expansion keine 1 enthält) - ihre Dichte beträgt n ^ (log_3 2) (ca. 0,631). Es reicht also nicht aus, zu überprüfen, ob die Menge nicht zu groß ist, und dann alle Paare zu überprüfen, um O (n log n) zu erhalten. Sie müssen die Sequenz intelligenter untersuchen. Eine bessere niedriger ist gebunden zitierte hier - es ist n 1-c / (log (n)) ^ (1/2) . Dies bedeutet, dass das Cantor-Set nicht optimal ist.

Obergrenze - mein alter Algorithmus:

Es ist bekannt, dass für großes n eine Teilmenge von {1,2, ..., n}, die keine arithmetische Folge enthält, höchstens n / (log n) ^ (1/20) Elemente enthält. Die Arbeit Über Tripel in arithmetischer Folge beweist mehr: Die Menge darf nicht mehr als n * 2 28 * (log log n / log n) 1/2 Elemente enthalten. Sie können also überprüfen, ob diese Grenze erreicht ist, und wenn nicht, naiv Paare überprüfen. Dies ist der O (n 2 * log log n / log n) -Algorithmus, schneller als O (n 2 ). Leider ist "On triples ..." auf Springer - aber die erste Seite ist verfügbar, und die Darstellung von Ben Green ist hier verfügbar , Seite 28, Satz 24.

Die Papiere stammen übrigens aus dem Jahr 1999 - im selben Jahr wie das erste, das ich erwähnt habe. Deshalb erwähnt das erste wahrscheinlich dieses Ergebnis nicht.


2
Tolle Antwort, die erste, die etwas Bestimmtes über dieses Problem aussagt. Die Cantor-ähnliche Menge hat also n ^ 0,63 1s, was bedeutet, dass der Algorithmus "Alle Paare von 1s prüfen" im schlimmsten Fall mindestens n ^ 1,26 (≫ n log n) beträgt . Die in Szemeredis Artikel zitierte Untergrenze (übrigens das von ihm zitierte Moser-Papier ist hier verfügbar: books.google.com/books?id=Cvtwu5vVZF4C&pg=PA245 ) scheint tatsächlich n ^ (2-o (1)) zu implizieren, aber wir müssen Seien Sie etwas vorsichtig, da dort Zahlen aus {1, ..., n} gezogen werden, aber hier ist es die Summe der Zahlen in der Reihenfolge, die n ist.
ShreevatsaR

Äh, was genau ist die "Cantor-ähnliche" Binärsequenz, die n ^ (log_3 2) 1s und keine drei gleichmäßig verteilten 1s enthält?
ShreevatsaR

Beispiel: 101000101000000000101000101. Seine Länge beträgt 3 ^ n und hat 2 ^ n Einsen (also n ^ 0,63 Dichte). Wenn Sie die Stellen von Einsen binär aufschreiben, ist dies {0,2,20,22,200,202,220,222}. Eine andere Möglichkeit, sich das vorzustellen, besteht darin, eine Folge von Einsen zu nehmen und "mittlere" kontinuierlich zu entfernen, wie bei der normalen Cantor-Set-Konstruktion: 111111111 -> 111000111 -> 101000101. Der Grund, warum es keine arithmetische Folge enthält, ist: wenn x , y, z bildeten eins, dann unterscheiden sich y = (x + z) / 2 und x und z unterscheiden sich an einer Expansionsstelle. Nehmen Sie den wichtigsten. Angenommen, x hat 0 und z hat 2. Dann muss y dort 1 haben. Widerspruch.
SDCVVC

3
Wieder tolle Forschung! Ich habe das 3SUM-Papier von 2008 weiterverfolgt und es bezog sich auf die CLRS-Übung. 30.1-7, nachdem ich mir angesehen habe, worauf ich die Antwort bekommen habe - der O (n log n) -Algorithmus ist eigentlich ganz einfach! (Nur ein Polynom / eine Erzeugungsfunktion quadrieren.) Ich habe die Antwort unten gepostet. (Jetzt trete ich mich dafür, dass ich nicht früher daran gedacht habe ... einfache Lösungen lösen immer diese Reaktion aus: p)
ShreevatsaR

Die Antwort auf seine Prüfungsfrage lautete also wie folgt: "Dieses Problem lässt sich auf das 3-SUM-Problem reduzieren, und 3-SUM hard hat keine subquadratische Lösung, sodass dieses Problem nicht in O (n logn) gelöst werden kann. "" Ja?
Hughdbrown

8

Dies ist keine Lösung, sondern eine ähnliche Denkrichtung wie Olexiy

Ich habe mit dem Erstellen von Sequenzen mit maximaler Anzahl von Einsen herumgespielt, und sie sind alle sehr interessant. Ich habe bis zu 125 Stellen erhalten. Hier sind die ersten drei Zahlen, die beim Versuch gefunden wurden, so viele '1'-Bits wie möglich einzufügen:

  • 1101100001101100000000000000110110000110110000000000000000000000000000000000000000000110110000110110000000000000011011000011011
  • 1011010001011010000000000001011010001011010000000000000000000000000000000000000000000101101000101101000000000000101101000101101
  • 1001100101001100100000000001001100101001100100000000000000000000000000000000000000011001010011001000000000011001010011001

Beachten Sie, dass es sich bei allen um Fraktale handelt (angesichts der Einschränkungen nicht allzu überraschend). Es kann etwas sein, rückwärts zu denken. Wenn die Zeichenfolge kein Fraktal mit einem Merkmal ist, muss sie ein sich wiederholendes Muster haben.

Vielen Dank an Beta für den besseren Begriff, um diese Zahlen zu beschreiben.

Update: Leider sieht es so aus, als würde das Muster zusammenbrechen, wenn mit einer ausreichend großen Anfangszeichenfolge begonnen wird, z. B.: 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
Holy * @ !!, das sind FRAKTALEN! Wenn dies zutrifft, wird die Anzahl der Einsen nach oben begrenzt und ist kleiner als O (n).
Beta

Fraktale, das ist ein viel besserer Begriff, um sie zu beschreiben. Danke
z -

Interessanterweise ähneln diese Muster stark der ternären Menge von Cantor ( en.wikipedia.org/wiki/Cantor_set ). Wenn dies so ist, dann muss der Anteil der Einsen gegen Null tendieren ...
Flybywire

Ist es offensichtlich, dass die Sequenzen mit der maximalen Anzahl von 1s ohne Tripel direkt für die Worst-Case-Laufzeit des Algorithmus relevant sind? Es ist denkbar, dass Sie Zeichenfolgen mit vielen Einsen haben, bei denen Sie die Tripel jedoch nur sehr spät finden, da sich diese Einsen an den Positionen befinden, die von Ihrem Algorithmus spät untersucht werden.
ShreevatsaR

3
Meine Analyse der Anzahl der Einsen in den Zeichenfolgen im Vergleich zu ihrer Gesamtgröße scheint darauf hinzudeuten, dass es eine lineare Beziehung zwischen der Anzahl der Einsen und der Größe der Zeichenfolge gibt, was mich zu der Annahme führt, dass es keine glückliche Obergrenze gibt, die uns sagen lässt, dass die Die Anzahl der Einsen in einer Zeichenfolge beträgt höchstens log (n) für eine bestimmte Zeichenfolge. Die Lösungen, die sich nur mit den Positionen der Einsen und nicht mit der gesamten Zeichenfolge selbst befassen, werden also auch O (n ^ 2) sein. Oder genauer gesagt O (n + m ^ 2), wobei m die Anzahl der Einsen in der Zeichenfolge ist und n die Größe der Zeichenfolge ist und m Big-Theta (n) ist.
Welbog

6

Ich vermute, dass ein einfacher Ansatz, der wie O (n ^ 2) aussieht, tatsächlich etwas Besseres ergibt, wie O (n ln (n)). Die Sequenzen, deren Test am längsten dauert (für jedes gegebene n), enthalten keine Trios und begrenzen die Anzahl der Einsen, die in der Sequenz enthalten sein können, stark.

Ich habe mir einige handwedelnde Argumente ausgedacht, aber ich konnte keinen ordentlichen Beweis finden. Ich werde einen Stich in die Dunkelheit machen: Die Antwort ist eine sehr kluge Idee, die der Professor so lange gekannt hat, dass es offensichtlich erscheint, aber es ist viel zu schwer für die Studenten. (Entweder das oder du hast die Vorlesung durchgeschlafen.)


2
lol, nein, ich habe keine Vorträge durchgeschlafen. Ich habe mit ein paar anderen Studenten gesprochen, und niemand hatte eine klare Vorstellung davon, wie man es lösen kann. Die meisten schrieben einige BS über Teilen und Erobern, um einen Teil der Anerkennung zu erhalten.
Robert Parker

3

Revision: 2009-10-17 23:00

Ich habe dies mit einer großen Anzahl (wie Zeichenfolgen von 20 Millionen) ausgeführt und glaube jetzt, dass dieser Algorithmus nicht O (n logn) ist. Trotzdem ist es eine ausreichend coole Implementierung und enthält eine Reihe von Optimierungen, die es sehr schnell laufen lassen. Es wertet alle Anordnungen von Binärzeichenfolgen mit 24 oder weniger Ziffern in weniger als 25 Sekunden aus.

Ich habe den Code aktualisiert, um die 0 <= L < M < U <= X-1Beobachtung von heute früher aufzunehmen.


Original

Dies ähnelt im Konzept einer anderen Frage, die ich beantwortet habe . Dieser Code untersuchte auch drei Werte in einer Reihe und stellte fest, ob ein Triplett eine Bedingung erfüllte. Hier ist der daraus angepasste C # -Code:

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

Die Hauptunterschiede sind:

  1. Umfassende Suche nach Lösungen
    Dieser Code generiert einen Datensatz, um die schwierigste Eingabe für diesen Algorithmus zu finden.
  2. Alle Lösungen versus am schwierigsten zu lösen
    Der Code für die vorherige Frage hat alle Lösungen mit einem Python-Generator generiert. Dieser Code zeigt nur das Schwierigste für jede Musterlänge an.
  3. Bewertungsalgorithmus
    Dieser Code überprüft den Abstand vom mittleren Element zum linken und rechten Rand. Der Python-Code testete, ob eine Summe über oder unter 0 lag.
  4. Konvergenz eines Kandidaten
    Der aktuelle Code arbeitet von der Mitte zum Rand, um einen Kandidaten zu finden. Der Code im vorherigen Problem arbeitete von den Rändern zur Mitte. Diese letzte Änderung führt zu einer großen Leistungsverbesserung.
  5. Verwendung von geraden und ungeraden Pools
    Basierend auf den Beobachtungen am Ende dieses Aufsatzes durchsucht der Code Paare von geraden Zahlen von Paaren von ungeraden Zahlen, um L und U zu finden, wobei M fest bleibt. Dies reduziert die Anzahl der Suchvorgänge durch Vorberechnung von Informationen. Dementsprechend verwendet der Code zwei Indirektionsebenen in der Hauptschleife von FindCandidate und erfordert zwei Aufrufe von FindCandidate für jedes mittlere Element: einmal für gerade Zahlen und einmal für ungerade.

Die allgemeine Idee besteht darin, an Indizes zu arbeiten, nicht an der Rohdarstellung der Daten. Durch die Berechnung eines Arrays, in dem die Einsen erscheinen, kann der Algorithmus zeitlich proportional zur Anzahl der Einsen in den Daten und nicht zeitlich proportional zur Länge der Daten ausgeführt werden. Dies ist eine Standardtransformation: Erstellen Sie eine Datenstruktur, die einen schnelleren Betrieb ermöglicht und gleichzeitig das Problem gleichwertig hält.

Die Ergebnisse sind veraltet: entfernt.


Bearbeiten: 2009-10-16 18:48

Bei den Daten von yx, die in den anderen Antworten als repräsentativ für die zu berechnenden harten Daten anerkannt sind, erhalte ich diese Ergebnisse ... Ich habe sie entfernt. Sie sind veraltet.

Ich möchte darauf hinweisen, dass diese Daten für meinen Algorithmus nicht am schwierigsten sind, daher denke ich, dass die Annahme, dass die Fraktale von yx am schwierigsten zu lösen sind, falsch ist. Ich gehe davon aus, dass der schlimmste Fall für einen bestimmten Algorithmus vom Algorithmus selbst abhängt und wahrscheinlich nicht über verschiedene Algorithmen hinweg konsistent ist.


Bearbeiten: 2009-10-17 13:30

Weitere Beobachtungen dazu.

Konvertieren Sie zunächst die Zeichenfolge von Nullen und Einsen in ein Array von Indizes für jede Position der Einsen. Angenommen, die Länge dieses Arrays A ist X. Dann ist das Ziel zu finden

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

so dass

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

oder

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

Da A [L] und A [U] eine gerade Zahl ergeben, können sie nicht (gerade, ungerade) oder (ungerade, gerade) sein. Die Suche nach einer Übereinstimmung könnte verbessert werden, indem A [] in ungerade und gerade Pools aufgeteilt wird und nach Übereinstimmungen auf A [M] in den Pools von ungeraden und geraden Kandidaten gesucht wird.

Dies ist jedoch eher eine Leistungsoptimierung als eine algorithmische Verbesserung, denke ich. Die Anzahl der Vergleiche sollte sinken, aber die Reihenfolge des Algorithmus sollte gleich sein.


Bearbeiten 2009-10-18 00:45

Eine weitere Optimierung fällt mir ein, genauso wie die Trennung der Kandidaten in gerade und ungerade. Da die drei Indizes zu einem Vielfachen von 3 addiert werden müssen (a, a + x, a + 2x - mod 3 ist 0, unabhängig von a und x), können Sie L, M und U in ihre mod 3-Werte trennen ::

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

Tatsächlich könnten Sie dies mit der geraden / ungeraden Beobachtung kombinieren und sie in ihre Mod 6-Werte aufteilen:

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

und so weiter. Dies würde eine weitere Leistungsoptimierung liefern, jedoch keine algorithmische Beschleunigung.


2

Konnte noch keine Lösung finden :(, habe aber einige Ideen.

Was ist, wenn wir von einem umgekehrten Problem ausgehen: Konstruieren Sie eine Sequenz mit der maximalen Anzahl von 1s und OHNE gleichmäßig verteilte Trios. Wenn Sie nachweisen können, dass die maximale Anzahl von Einsen o (n) ist, können Sie Ihre Schätzung verbessern, indem Sie nur die Liste der Einsen durchlaufen.


Nun, die Anzahl der Einsen ist sicherlich oben durch O (n) begrenzt. Es kann nicht O (n ** 2) sein, richtig - die Anzahl der Einsen wächst schneller als die Daten? Die wichtige Frage ist, ob die Obergrenze niedriger ist.
Hughdbrown

Ich habe das kleine o benutzt, nicht das große
Olexiy

2

Das kann helfen ....

Dieses Problem reduziert sich auf Folgendes:

Suchen Sie bei einer gegebenen Folge positiver Ganzzahlen eine zusammenhängende Teilsequenz, die in ein Präfix und ein Suffix unterteilt ist, sodass die Summe des Präfixes der Teilsequenz gleich der Summe des Suffixes der Teilsequenz ist.

Zum Beispiel [ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ]würden wir bei einer gegebenen Folge von eine Teilfolge von [ 3, 6, 5, 2, 2]mit einem Präfix von [ 3, 6 ]mit Präfixsumme von 9und einem Suffix von [ 5, 2, 2 ]mit Suffixsumme von finden 9.

Die Reduzierung ist wie folgt:

Wenn Sie eine Folge von Nullen und Einsen haben und ganz links beginnen, bewegen Sie sich weiter nach rechts. Notieren Sie jedes Mal, wenn eine andere angetroffen wird, die Anzahl der Züge seit der vorherigen und fügen Sie diese Anzahl an die resultierende Sequenz an.

Zum Beispiel [ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ]würden wir bei einer gegebenen Folge von die Reduktion von finden [ 1, 3, 4]. Aus dieser Reduktion berechnen wir die zusammenhängende Teilfolge von [ 1, 3, 4], das Präfix von [ 1, 3]mit Summe von 4und das Suffix von [ 4 ]mit Summe von 4.

Diese Reduzierung kann in berechnet werden O(n).

Leider bin ich mir nicht sicher, wohin ich von hier aus gehen soll.


1
Es ist eine kompaktere Notation, aber es hilft nicht der zeitlichen Komplexität. Die Menge der "Präfix" -Partitionen ist isomorph zu einer Suche aller Paare in allen Vorkommen von "1", was O (n ^ 2) ist.
p00ya

Es gibt anscheinend Algorithmen, die sich mit zusammenhängenden Teilsequenzen befassen. Leider scheinen sie alle damit zu tun zu haben, die zusammenhängende Teilfolge mit der maximalen Summe in O (n) zu finden.
Yfeldblum

@ p00ya das ist nicht richtig. Unter Verwendung dieses Algorithmus hängt die Zeitkoplexität von der Obergrenze der Anzahl falscher ab, die durch Assupton auf einer von Cantor erzeugten Zeichenfolge ((3/2) ^ (log (n) / log (3))) ist, und die Raumkomplexität wird dies, aber Zeitkomplexität wird dies multipliziert mit n. Überprüfen Sie meine zweite Antwort. (nicht die negative): D
Luka Rahne

@ralu: Das ist unter Ihrer Annahme, dass von Cantor generierte Zeichenfolgen der schlimmste Fall sind, was falsch ist. Für die Aufzeichnung ist die Anzahl der Paare sicherlich O (n ^ 2); aber ich denke, ich habe wirklich impliziert, dass es Big-Omega (n ^ 2) war, was angesichts dieser Ergebnisse falsch ist (siehe insbesondere NrootN-Link), was auf eine Untergrenze in den Paaren von Big-Omega (n ^ (2 / 1,52) hindeutet )) durch Beweis oder Big-Omega (n ^ (4/3)) durch Vermutung.
p00ya

1

Für den einfachen Problemtyp (dh Sie suchen drei "1" mit nur (dh null oder mehr) "0" dazwischen) ist es ganz einfach: Sie können die Sequenz einfach bei jeder "1" aufteilen und nach zwei benachbarten Teilsequenzen mit suchen die gleiche Länge (die zweite Teilsequenz ist natürlich nicht die letzte). Offensichtlich kann dies in O (n) Zeit erfolgen.

Für die komplexere Version (dh Sie suchen einen Index i und eine Lücke g > 0 so s[i]==s[i+g]==s[i+2*g]=="1"), bin ich mir nicht sicher, ob es eine O (n log n) -Lösung gibt, da möglicherweise O (n²) -Tripletts vorhanden sind diese Eigenschaft (denken Sie an eine Folge von allen, es gibt ungefähr n² / 2 solcher Drillinge). Natürlich suchen Sie nur eine davon, aber ich habe derzeit keine Ahnung, wie ich sie finden kann ...


Ja, wir diskutieren die schwierigere Version des Problems. Dennoch kann die n * log (n) -Lösung möglich sein.
Olexiy

1
Es gibt tatsächlich n wähle 3, was O (n ^ 3) mögliche Dreifache ist. Ich denke, als du ungefähr n ^
2/2

@gmatt: n wähle 2 ist genug; Wenn wir zwei Einsen festlegen, wird die Position der dritten bestimmt und es ist eine konstante Zeit, um zu sehen, ob sich an dieser Position eine 1 befindet oder nicht.
ShreevatsaR

@ShreevatsaR: Ja, das ist richtig, ich denke, ich habe an den ungezwungenen Fall gedacht.
ldog

1
@gmatt: Eigentlich suchen wir nach Tupeln (i, g) wie oben definiert mit den Einschränkungen 0 <= i <(n-3) und 0 <g <(ni-1) / 2, daher die Schätzung von n ^
2/2

1

Eine lustige Frage, aber sobald Sie feststellen, dass das tatsächliche Muster zwischen zwei Einsen keine Rolle spielt, wird der Algorithmus zu:

  • scannen suchen nach einer '1'
  • ab der nächsten Position nach einer weiteren '1' suchen (bis zum Ende des Arrays abzüglich des Abstands von der aktuellen ersten '1' oder der dritten '1' wäre außerhalb der Grenzen)
  • Wenn an der Position der 2. '1' plus dem Abstand zur ersten 1 'eine dritte' 1 'gefunden wird, haben wir gleichmäßig Leerzeichen.

In Code, JTest-Mode (Beachten Sie, dass dieser Code nicht so effizient geschrieben wurde, und ich habe einige Drucke hinzugefügt, um zu sehen, was passiert.)

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
Wenn ich mich nicht irre, ist dies O (n²), da die äußere Schleife n-mal und die innere Schleife durchschnittlich n / 2-mal läuft.
StriplingWarrior

Die äußere Schleife läuft n-mal und die innere Schleife läuft durchschnittlich n / 4, wird jedoch nur von Positionen nach einer '1' gestartet. Um sich einem n ^ 2-Verhalten anzunähern, muss die Anzahl der Einsen hoch sein, was frühzeitig zu einem echten Ergebnis führt, wodurch die Verarbeitung gestoppt wird. Daher wird das n ^ 2-Verhalten niemals auftreten. Wie man ein O basierend auf bekannten Eigenschaften der Daten bestimmt, entgeht mir im Moment.
rsp

Leider geht es nicht um die durchschnittliche Laufzeit im realen Leben, sondern um die theoretische Big O-Laufzeit. Und dein Ansatz ist O (n²) (genau wie meiner, weil dein Ansatz der gleiche ist wie meiner)
DaClown

Ich habe nicht über durchschnittliches Verhalten gesprochen, sondern über maximales Verhalten. Es würde mich nicht wundern, wenn nachweislich die maximale Entropie, die den Test nicht besteht, log n '1 in der Zeichenfolge enthält.
rsp

Was ist, wenn Sie den Index in der äußeren Schleife mit dem der ersten 1 in der inneren Schleife aktualisieren, dh wenn (one [m] == ONE) {n = m}? Hilft das dem großen O?
Dampfer25

1

Ich dachte an einen Divide-and-Conquer-Ansatz, der funktionieren könnte.

Zunächst müssen Sie bei der Vorverarbeitung alle Zahlen, die kleiner als die Hälfte Ihrer Eingabegröße ( n / 3) sind, in eine Liste einfügen .

Gegeben eine Zeichenfolge: 0000010101000100(Beachten Sie, dass dieses spezielle Beispiel gültig ist)

Fügen Sie alle Primzahlen (und 1) von 1 bis (16/2) in eine Liste ein: {1, 2, 3, 4, 5, 6, 7}

Dann teilen Sie es in zwei Hälften:

100000101 01000100

Machen Sie so weiter, bis Sie zu Zeichenfolgen der Größe 1 gelangen. Fügen Sie für alle Zeichenfolgen der Größe 1 mit einer 1 den Index der Zeichenfolge zur Liste der Möglichkeiten hinzu. Andernfalls geben Sie -1 für einen Fehler zurück.

Sie müssen auch eine Liste der noch möglichen Abstandsabstände zurückgeben, die jedem Startindex zugeordnet sind. (Beginnen Sie mit der Liste, die Sie oben erstellt haben, und entfernen Sie die Zahlen, während Sie fortfahren.) Hier bedeutet eine leere Liste, dass Sie nur mit einer 1 arbeiten und daher an dieser Stelle ein beliebiger Abstand möglich ist. Andernfalls enthält die Liste Abstände, die ausgeschlossen werden müssen.

Fahren Sie also mit dem obigen Beispiel fort:

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

Im ersten Kombinationsschritt haben wir jetzt acht Zweiergruppen. Im ersten Fall haben wir die Möglichkeit einer Menge, aber wir lernen, dass ein Abstand von 1 unmöglich ist, weil die andere Null da ist. Wir geben also 0 (für den Index) und {2,3,4,5,7} zurück, da ein Abstand von 1 unmöglich ist. Im zweiten haben wir nichts und geben so -1 zurück. Im dritten haben wir eine Übereinstimmung ohne Abstände in Index 5, also geben Sie 5, {1,2,3,4,5,7} zurück. Im vierten Paar geben wir 7 zurück, {1,2,3,4,5,7}. Im fünften geben Sie 9, {1,2,3,4,5,7} zurück. Im sechsten Fall geben Sie -1 zurück. Im siebten geben Sie 13 zurück, {1,2,3,4,5,7}. Im achten geben Sie -1 zurück.

Wenn wir noch einmal vier Vierergruppen kombinieren, haben wir:

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

Kombinieren zu Achtergruppen:

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

Kombinieren zu einem Satz von 16:

10000101 01000100

Im weiteren Verlauf prüfen wir alle Möglichkeiten. Bis zu diesem Schritt haben wir Dinge hinterlassen, die über das Ende der Zeichenfolge hinausgingen, aber jetzt können wir alle Möglichkeiten prüfen.

Grundsätzlich überprüfen wir die erste 1 mit Abständen von 5 und 7 und stellen fest, dass sie nicht mit 1 übereinstimmen. (Beachten Sie, dass jede Prüfung KONSTANT und nicht linear ist.) Dann prüfen wir die zweite (Index 5) mit Abständen von 2, 3, 4, 5, 6 und 7 - oder wir würden, aber wir können seitdem bei 2 anhalten das passt tatsächlich zusammen.

Puh! Das ist ein ziemlich langer Algorithmus.

Ich weiß nicht 100%, ob es wegen des letzten Schritts O (n log n) ist , aber alles bis dahin ist definitiv O (n log n) , soweit ich das beurteilen kann. Ich werde später darauf zurückkommen und versuchen, den letzten Schritt zu verfeinern.

EDIT: Meine Antwort wurde geändert, um Welbogs Kommentar widerzuspiegeln. Entschuldigung für den Fehler. Ich werde später auch einen Pseudocode schreiben, wenn ich etwas mehr Zeit habe, um zu entziffern, was ich wieder geschrieben habe. ;-);


Ich folge nicht Ihrem Algorithmus, sondern +1 für den Versuch eines Algorithmus, der tatsächlich versucht, O (n log n) zu sein
ldog

Vielen Dank. Ich werde versuchen, es besser zu erklären, wenn ich mehr Zeit habe (vielleicht schreibe ich einen Pseudocode oder so).
Platinum Azure

Warum betrachten Sie nur Lückenmöglichkeiten von Primzahlen? Wie würden Sie vorschlagen, eine Zeichenfolge wie 100010001? Wenn ich Ihren Ansatz richtig verstehe, kann er nicht mit ihm übereinstimmen, da die richtige Antwort (0,{4})nicht berechnet werden kann. Angesichts der Tatsache, dass Sie keine Primzahlen in Ihrer Liste benötigen, ist es einfach, pathologische Zeichenfolgen zu finden, die die Liste der Möglichkeiten, die Sie überprüfen müssen, auf einen höheren Wert als O (n log (n)) aufblähen, denke ich.
Welbog

schwört Nun, ich wollte ursprünglich ein Vielfaches machen, aber ich habe meine Antwort auf halbem Weg geändert und konnte nicht alles ändern. Es tut uns leid. Wird in Kürze behoben
Platinum Azure

3
Ich glaube nicht, dass es O (n log n) ist. Im ersten Kombinationsschritt behandeln Sie (n / 2) Sätze, von denen jeder möglicherweise einen Satz von O (n) möglichen Abständen zurückgibt. Dies allein macht es leider O (n ^ 2).
MartinStettner

1

Ich werde hier meine grobe Vermutung geben und diejenigen, die die Komplexität besser berechnen können, mir helfen, wie mein Algorithmus in O-Notation abschneidet

  1. gegebene Binärzeichenfolge 0000010101000100 (als Beispiel)
  2. Kopf und Schwanz von Nullen beschneiden -> 00000 101010001 00
  3. Wir erhalten 101010001 aus der vorherigen Berechnung
  4. Überprüfen Sie, ob das mittlere Bit 'Eins' ist. Wenn dies zutrifft, werden drei gleichmäßig verteilte 'Einsen' gefunden (nur wenn die Anzahl der Bits ungerade ist).
  5. Wenn die verbleibende zugeschnittene Anzahl von Bits gerade nummeriert ist, können Kopf und Schwanz 'Eins' nicht Teil eines gleichmäßig beabstandeten 'Eins' sein.
  6. Wir verwenden 1010100001 als Beispiel (mit einer zusätzlichen 'Null', um eine gerade nummerierte Ernte zu erhalten). In diesem Fall müssen wir erneut beschneiden und werden dann -> 10101 00001
  7. Wir erhalten 10101 aus der vorherigen Berechnung und überprüfen das mittlere Bit, und wir haben das Bit mit gleichmäßigem Abstand wiedergefunden

Ich habe keine Ahnung, wie ich die Komplexität dafür berechnen soll. Kann mir jemand helfen?

Bearbeiten: Fügen Sie Code hinzu, um meine Idee zu veranschaulichen

edit2: habe versucht meinen Code zu kompilieren und habe einige große Fehler gefunden, behoben

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 Ich denke, es wird funktionieren, wenn es den Aufruf find3even (Kopf + 1, Schwanz) erreicht hat, der es dann auf 111 bei Kopf = 4 zuschneidet. Könnten Sie noch einmal nach mir suchen?
Andycjw

@recursive Bitte überprüfen Sie den Code, den ich hinzugefügt habe, um den Pseudo-Code, den ich zuvor erstellt habe, besser zu erklären, der nicht sehr streng und prägnant ist
andycjw

Dies ist nlogn - für n Bits erwarten wir ungefähr logn Iterationen, die n * c Bits prüfen, wobei C eine Konstante ist.
Ron Warholic

Ja, dies scheint bei 111001 und 100111 als einfachste Fälle zu scheitern. Die gleichmäßig verteilten Einsen müssen nicht auf dem mittleren Bit zentriert sein.
Dean J

Es behandelt diese Fälle korrekt, 111001 hat eine gerade Anzahl von Bits, so dass es sofort in 111 und 001 aufgeteilt wird. Da 111 eine ungerade Anzahl von Bits hat und das mittlere Bit eines ist, wird es erfolgreich zurückgegeben.
Ron Warholic

1

Ich habe mir so etwas ausgedacht:

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

Dies ist inspiriert von andycjw.

  1. Schneiden Sie die Nullen ab.
  2. Wenn selbst dann testen Sie zwei Teilzeichenfolgen 0 - (len-2) (letztes Zeichen überspringen) und von 1 - (len-1) (überspringen Sie das erste Zeichen)
  3. Wenn nicht einmal, wenn der mittlere Saibling einer ist, dann haben wir Erfolg. Andernfalls teilen Sie die Zeichenfolge in der Mitte ohne das Element midle und überprüfen Sie beide Teile.

In Bezug auf die Komplexität könnte dies O (nlogn) sein, da wir bei jeder Rekursion durch zwei teilen.

Ich hoffe es hilft.


Es sieht so aus, als würden Sie ein Problem mit N Elementen in 2 Probleme mit N-1 Elementen konvertieren. Eine Halbierung würde bedeuten, dass es in zwei Probleme mit N / 2 Elementen umgewandelt wird.
RHSeeger

Dies ist nur bei geraden Längen der Fall. Wenn also len 8 ist, erstellt der Algorithmus Zeichenfolgen mit der Länge: 7, 7, 3, 3, 3, 3. Die Höhe des Rekursionsbaums beträgt 3 und das entspricht lg (8).
Beku

1

Ok, ich werde das Problem noch einmal untersuchen. Ich denke, ich kann einen O (n log (n)) - Algorithmus beweisen, der den bereits diskutierten ähnlich ist, indem ich einen ausgeglichenen Binärbaum verwende, um Abstände zwischen Einsen zu speichern. Dieser Ansatz wurde von der Beobachtung von Justice inspiriert, das Problem auf eine Liste von Entfernungen zwischen den Einsen zu reduzieren.

Könnten wir die Eingabezeichenfolge scannen, um einen ausgeglichenen Binärbaum um die Position von 1 zu erstellen, so dass jeder Knoten die Position der 1 speichert und jede Kante mit dem Abstand zur benachbarten 1 für jeden untergeordneten Knoten gekennzeichnet ist. Beispielsweise:

10010001 gives the following tree

      3
     / \
  2 /   \ 3
   /     \
  0       7

Dies kann in O (n log (n)) erfolgen, da für eine Zeichenfolge der Größe n jede Einfügung im schlimmsten Fall O (log (n)) benötigt.

Dann besteht das Problem darin, den Baum zu durchsuchen, um festzustellen, ob an einem Knoten ein Pfad von diesem Knoten durch das linke Kind vorhanden ist, der dieselbe Entfernung hat wie ein Pfad durch das rechte Kind. Dies kann für jeden Teilbaum rekursiv erfolgen. Beim Zusammenführen von zwei Teilbäumen in der Suche müssen die Abstände von Pfaden im linken Teilbaum mit den Abständen von Pfaden im rechten Teilbaum verglichen werden. Da die Anzahl der Pfade in einem Teilbaum proportional zu log (n) ist und die Anzahl der Knoten n ist, glaube ich, dass dies in O (n log (n)) Zeit erfolgen kann.

Habe ich etwas vergessen?


"Da die Anzahl der Pfade in einem Teilbaum proportional zu log (n) ist" Warum nicht n? Im Allgemeinen ist dies ein vielversprechender Ansatz.
SDCVVC

@sdcwc: Es ist proportional zu log (n) und nicht zu n, da in einem ausgeglichenen Baum jeder Teilbaum die Hälfte der Knoten hat und die Anzahl der Pfade zur Wurzel des Teilbaums der Anzahl der Knoten im Teilbaum entspricht (ohne die Wurzel).
Jeremy Bourque

0

Dies schien ein lustiges Problem zu sein, also beschloss ich, es zu versuchen.

Ich gehe davon aus, dass 111000001 die ersten drei finden und erfolgreich sein würde. Im Wesentlichen ist die Anzahl der Nullen nach der 1 wichtig, da 0111000 gemäß Ihrer Definition mit 111000 identisch ist. Sobald Sie zwei Fälle von 1 gefunden haben, vervollständigt die nächste gefundene 1 die Trilogie.

Hier ist es 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

Dies ist ein erster Versuch, daher bin ich sicher, dass dies sauberer geschrieben werden könnte. Bitte listen Sie die Fälle auf, in denen diese Methode fehlschlägt.


@recursive, diese sind nicht gleichmäßig verteilt.
James McMahon

Was meinst du mit gleichmäßig verteilt? Schauen Sie sich die Indizes 0, 3 und 6 an. Alle mit jeweils zwei Trennzeichen.
rekursiv

Oh, ich verstehe, so wie ich es verstanden habe, waren Nullen nur im Abstand enthalten.
James McMahon

Die Frage erwähnt "1001011", bei dem dies nicht funktioniert. Es gab eine frühere (jetzt gelöschte) Antwort, die unmittelbar nach dem Stellen der Frage veröffentlicht wurde und das gleiche (andere) Problem wie dieses löste. :-)
ShreevatsaR

Ich habe mir das heute bei der Arbeit angesehen und nicht verstanden, was Rob mit seiner Bearbeitung meinte. Ich habe die Frage aus Gründen der Klarheit bearbeitet. Ich hätte wissen müssen, dass mir etwas gefehlt hat, als ich es leicht hatte.
James McMahon

0

Ich gehe davon aus, dass der Grund dafür, dass dies nlog (n) ist, auf Folgendes zurückzuführen ist:

  • Um die 1 zu finden, die der Beginn des Tripletts ist, müssen Sie (n-2) Zeichen überprüfen. Wenn Sie es zu diesem Zeitpunkt noch nicht gefunden haben, werden Sie es nicht tun (Zeichen n-1 und n können kein Triplett starten) (O (n))
  • Um die zweite 1 zu finden, die der Teil des Tripletts ist (beginnend mit dem ersten), müssen Sie m / 2 (m = nx, wobei x der Versatz der ersten 1 ist) überprüfen. Dies liegt daran, dass Sie, wenn Sie die zweite 1 nicht gefunden haben, bis Sie auf halbem Weg von der ersten bis zum Ende sind, dies nicht tun werden ... da die dritte 1 genau den gleichen Abstand nach der zweiten haben muss.(O (log (n)))
  • Es ist O (1), die letzte 1 zu finden, da Sie den Index kennen, bei dem sie sein muss, wenn Sie die erste und die zweite finden.

Sie haben also n, log (n) und 1 ... O (nlogn)

Edit: Ups, mein schlechtes. Mein Gehirn hatte es so eingestellt, dass n / 2 logn war ... was es offensichtlich nicht ist (das Verdoppeln der Anzahl von Elementen verdoppelt immer noch die Anzahl von Iterationen in der inneren Schleife). Dies ist immer noch bei n ^ 2 und löst das Problem nicht. Na ja, zumindest muss ich Code schreiben :)


Implementierung 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

Ich glaube, ich habe einen Weg gefunden, das Problem zu lösen, aber ich kann keinen formalen Beweis erstellen. Die Lösung, die ich gemacht habe, ist in Java geschrieben und verwendet einen Zähler 'n', um zu zählen, wie viele Listen- / Array-Zugriffe es macht. Daher sollte n kleiner oder gleich stringLength * log (stringLength) sein, wenn es korrekt ist. Ich habe es für die Zahlen 0 bis 2 ^ 22 versucht, und es funktioniert.

Zunächst wird die Eingabezeichenfolge durchlaufen und eine Liste aller Indizes erstellt, die eine Eins enthalten. Dies ist nur O (n).

Dann wählt es aus der Liste der Indizes einen ersten Index und einen zweiten Index aus, der größer als der erste ist. Diese beiden Indizes müssen diejenigen enthalten, da sie in der Liste der Indizes enthalten sind. Von dort kann der dritte Index berechnet werden. Wenn der inputString [dritterIndex] eine 1 ist, wird er angehalten.

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;

}}

Zusätzlicher Hinweis: Der Zähler n wird nicht inkrementiert, wenn er über die Eingabezeichenfolge iteriert, um die Liste der Indizes zu erstellen. Diese Operation ist O (n), hat also ohnehin keinen Einfluss auf die Komplexität des Algorithmus.


Sie scheinen immer noch zwei verschachtelte Schleifen von O (n) zu haben, was es zu O (n ^ 2) macht
RHSeeger

Das Indexarray hat nicht die gleiche Größe wie die Eingabezeichenfolge. Dies macht es mir schwer, einen echten Beweis zu schreiben oder zu beweisen, dass er falsch ist. Ich vermute, dass es eine mathematische Idee gibt, die diese Arbeit macht.
Robert Parker

1
Ich denke, der Trick bei diesem Problem besteht darin, dass der schlimmste Fall eines Strings, den Sie erhalten können, trotz O (n ^ 2) nur zu O (nlogn) -Iterationen führt, andernfalls haben Sie mit Ihrem Algorithmus eine Lösung gefunden.
z -

2
Das Testen auf 2 ^ 22 testet die Komplexität nicht wirklich. 2 ^ 22 hat nur 22 Bits, was bedeutet, dass Ihr N 22 ist. Versuchen Sie es für einige Werte, wobei N einige Millionen beträgt.
Peter Recore

1
Versuchen Sie diesen Algorithmus mit einer der maximalen "schlechten" Zeichenfolgen, die in der Antwort von yx angegeben sind, und Sie werden feststellen, dass dies ein O(n^2)Algorithmus ist.
Welbog

0

Ein Einstieg in das Problem besteht darin, über Faktoren nachzudenken und sich zu verändern.

Beim Verschieben vergleichen Sie die Folge von Einsen und Nullen mit einer verschobenen Version von sich. Sie nehmen dann passende. Nehmen Sie dieses Beispiel um zwei verschoben:

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

Die resultierenden Einsen (bitweise UND-verknüpft) müssen alle Einsen darstellen, die gleichmäßig von zwei beabstandet sind. Das gleiche Beispiel um drei verschoben:

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

In diesem Fall gibt es keine Einsen, die gleichmäßig drei voneinander entfernt sind.

Was sagt dir das? Nun, dass Sie nur Schichten testen müssen, die Primzahlen sind. Angenommen, Sie haben zwei Einsen, die sechs voneinander entfernt sind. Sie müssten nur 'zwei' Schichten und 'drei' Schichten testen (da diese sechs teilen). Beispielsweise:

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

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

Die einzigen Verschiebungen, die Sie jemals überprüfen müssen, sind 2,3,5,7,11,13 usw. Bis zur Primzahl, die der Quadratwurzel der Größe der Ziffernfolge am nächsten liegt.

Fast gelöst?

Ich denke, ich bin einer Lösung näher. Grundsätzlich:

  1. Scannen Sie die Zeichenfolge nach Einsen. Für jede 1 Note ist es der Rest, nachdem ein Modul seiner Position genommen wurde. Der Modul reicht von 1 bis zur Hälfte der Saitengröße. Dies liegt daran, dass die größtmögliche Trennungsgröße die Hälfte der Zeichenfolge beträgt. Dies geschieht in O (n ^ 2). ABER. Es müssen nur Primmodule überprüft werden, damit O (n ^ 2 / log (n))
  2. Sortieren Sie die Liste der Module / Reste zuerst nach dem größten Modul. Dies kann in O (n * log (n)) erfolgen.
  3. Suchen Sie nach drei aufeinanderfolgenden Modulen / Resten, die gleich sind.
  4. Irgendwie die Position der einen abrufen!

Ich denke, der größte Hinweis auf die Antwort ist, dass die schnellsten Sortieralgorithmen O (n * log (n)) sind.

FALSCH

Schritt 1 ist falsch, wie ein Kollege betont hat. Wenn wir Einsen an Position 2,12 und 102 haben, dann würden sie bei einem Modul von 10 alle die gleichen Reste haben und sind dennoch nicht gleich weit voneinander entfernt! Es tut uns leid.


Dies ist ein interessanter Ansatz. Lassen Sie uns wissen, wenn Sie eine vollständige Lösung finden.
James McMahon

Eine Verschiebung um eine Zahl k O (n) mal und dann eine Überprüfung von O (n) pro Schicht ergibt einen O (n ^ 2) -Algorithmus, selbst wenn Sie um eine Zahl verschoben haben. Ihr Algorithmus müsste sich um mehr als eine Zahl verschieben.
ldog

0

Hier sind einige Gedanken, die sich trotz meiner Bemühungen nicht in einen Bogen zu wickeln scheinen. Dennoch könnten sie ein nützlicher Ausgangspunkt für die Analyse einer Person sein.

Betrachten Sie die vorgeschlagene Lösung wie folgt. Dies ist der Ansatz, den mehrere Leute vorgeschlagen haben, einschließlich meiner selbst in einer früheren Version dieser Antwort. :)

  1. Schneiden Sie führende und nachfolgende Nullen.
  2. Scannen Sie die Zeichenfolge nach Einsen.
  3. Wenn eine 1 gefunden wird:
    1. Angenommen, es ist die Mitte 1 der Lösung.
    2. Verwenden Sie für jede vorherige 1 die gespeicherte Position, um die erwartete Position der letzten 1 zu berechnen.
    3. Wenn die berechnete Position nach dem Ende der Zeichenfolge liegt, kann sie nicht Teil der Lösung sein. Löschen Sie die Position daher aus der Liste der Kandidaten.
    4. Überprüfen Sie die Lösung.
  4. Wenn die Lösung nicht gefunden wurde, fügen Sie die aktuelle 1 zur Kandidatenliste hinzu.
  5. Wiederholen, bis keine Einsen mehr gefunden werden.

Betrachten Sie nun Eingabezeichenfolgen wie die folgenden, für die es keine Lösung gibt:

101
101001
1010010001
101001000100001
101001000100001000001

Im Allgemeinen ist dies die Verkettung von k Zeichenketten der Form j 0, gefolgt von einer 1 für j von Null bis k-1.

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

Es ist zu beachten, dass die Längen der Teilzeichenfolgen 1, 2, 3 usw. sind. Die Problemgröße n hat also Teilzeichenfolgen der Längen 1 bis k, so dass n = k (k + 1) / 2 ist.

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

Beachten Sie, dass k auch die Anzahl der Einsen verfolgt, die wir berücksichtigen müssen. Denken Sie daran, dass wir jedes Mal, wenn wir eine 1 sehen, alle bisher gesehenen 1 berücksichtigen müssen. Wenn wir also die zweite 1 sehen, betrachten wir nur die erste, wenn wir die dritte 1 sehen, überdenken wir die ersten beiden, wenn wir die vierte 1 sehen, müssen wir die ersten drei überdenken und so weiter. Am Ende des Algorithmus haben wir k (k-1) / 2 Paare von Einsen betrachtet. Nennen Sie das 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

Die Beziehung zwischen n und p ist, dass n = p + k ist.

Das Durchlaufen der Zeichenfolge dauert 0 (n) Zeit. Jedes Mal, wenn eine 1 angetroffen wird, werden maximal (k-1) Vergleiche durchgeführt. Da n = k (k + 1) / 2 ist, ist n> k ** 2, also sqrt (n)> k. Dies ergibt O (n sqrt (n)) oder O (n ** 3/2). Beachten Sie jedoch, dass dies möglicherweise keine wirklich enge Grenze ist, da die Anzahl der Vergleiche von 1 bis maximal k reicht und es nicht die ganze Zeit k ist. Aber ich bin mir nicht sicher, wie ich das in der Mathematik erklären soll.

Es ist immer noch nicht O (n log (n)). Ich kann auch nicht beweisen, dass diese Eingaben die schlimmsten Fälle sind, obwohl ich vermute, dass dies der Fall ist. Ich denke, eine dichtere Packung von 1 nach vorne führt zu einer noch spärlicheren Packung am Ende.

Da es vielleicht noch jemand nützlich findet, ist hier mein Code für diese Lösung 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";

Ihre "Nicht-Lösungs" -Sequenz ist falsch. Der Index jeder 1 ist die Folge der Dreieckszahlen 1, 3, 6, 10, 15 ... usw. und enthält die Zahlen 6, 36 und 66, die eine arithmetische Folge bilden.
Jason S

0

Fügen Sie beim Scannen von 1s ihre Positionen einer Liste hinzu. Vergleichen Sie die zweiten und aufeinanderfolgenden Einsen mit jeder Position in der Liste, die Sie bisher erstellt haben. Der Abstand entspricht currentOne (Mitte) - previousOne (links). Das Bit auf der rechten Seite ist currentOne + Abstand. Wenn es 1 ist, das Ende.

Die Liste der Einsen wächst umgekehrt mit dem Abstand zwischen ihnen. Einfach ausgedrückt, wenn Sie zwischen den Einsen viele Nullen haben (wie im schlimmsten Fall), wächst Ihre Liste der bekannten Einsen ziemlich langsam.

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

Ich dachte, ich würde einen Kommentar hinzufügen, bevor ich die 22. naive Lösung für das Problem veröffentliche. Für die naive Lösung müssen wir nicht zeigen, dass die Anzahl der Einsen in der Zeichenfolge höchstens O (log (n)) beträgt, sondern höchstens O (sqrt (n * log (n)).

Löser:

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

Es ist im Grunde ein bisschen ähnlich wie die Idee und Implementierung von flybywire, obwohl es nach vorne statt nach hinten schaut.

Gieriger 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

(Zu meiner Verteidigung bin ich immer noch in der Phase des "Learn Python" -Verständnisses)

Als potenziell nützliche Ausgabe des gierigen Aufbaus von Saiten gibt es einen ziemlich konstanten Sprung, nachdem eine Potenz von 2 in der Anzahl der Einsen erreicht wurde ... was ich nicht warten wollte, um Zeuge des Treffens von 2096 zu werden.

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

Ich werde versuchen, einen mathematischen Ansatz vorzustellen. Dies ist eher ein Anfang als ein Ende, daher wird jede Hilfe, jeder Kommentar oder sogar jeder Widerspruch zutiefst geschätzt. Wenn sich dieser Ansatz jedoch bewährt hat, ist der Algorithmus eine einfache Suche in der Zeichenfolge.

  1. Bei einer festgelegten Anzahl von Leerzeichen kund einer Zeichenfolge Sdauert die Suche nach einem Triplett mit k Abständen O(n)- Wir testen einfach für jedes 0<=i<=(n-2k)Wenn S[i]==S[i+k]==S[i+2k]. Der Test dauert O(1)und wir machen es n-kmal wo keine Konstante ist, also dauert es O(n-k)=O(n).

  2. Nehmen wir an, dass es einen umgekehrten Anteil zwischen der Anzahl der 1und den maximalen Leerzeichen gibt, nach denen wir suchen müssen. Das heißt, wenn es viele 1gibt, muss es ein Triplett geben und es muss ziemlich dicht sein; Wenn es nur wenige gibt 1, kann das Triplett (falls vorhanden) ziemlich spärlich sein. Mit anderen Worten, ich kann beweisen, dass ein 1solches Triplett existieren muss , wenn ich genug habe - und je mehr 1ich habe, desto dichter muss das Triplett gefunden werden. Dies kann durch das Pigeonhole-Prinzip erklärt werden - ich hoffe, darauf später näher eingehen zu können.

  3. Angenommen, Sie haben eine Obergrenze kfür die mögliche Anzahl von Leerzeichen, nach denen ich suchen muss. Nun, für jede 1Lage in S[i]wir überprüfen müssen 1in S[i-1]und S[i+1], S[i-2]und S[i+2], ... S[i-k]und S[i+k]. Dies geschieht O((k^2-k)/2)=O(k^2)für jede 1in S- aufgrund Gauss' Series Summenformel . Beachten Sie, dass dies von Abschnitt 1 abweicht - ich habe kals Obergrenze für die Anzahl der Leerzeichen, nicht als konstantes Leerzeichen.

Wir müssen beweisen O(n*log(n)). Das heißt, wir müssen zeigen, dass dies k*(number of 1's)proportional zu istlog(n) .

Wenn wir das schaffen, ist der Algorithmus trivial - für jeden, 1in Sdessen Index sich befindet i, suchen Sie einfach nach 1's von jeder Seite bis zur Entfernung k. Wenn zwei in der gleichen Entfernung gefunden wurden, kehren Sie zurück iund k. Wieder wäre der schwierige Teil zu findenk die Richtigkeit und zu beweisen.

Ich würde mich sehr über Ihre Kommentare hier freuen - ich habe bisher erfolglos versucht, die Beziehung zwischen kund die Anzahl der 1auf meinem Whiteboard zu finden .


0

Annahme:

Es ist einfach falsch, über die log (n) Anzahl der Obergrenzen von Einsen zu sprechen

BEARBEITEN:

Jetzt fand ich heraus, dass bei Verwendung von Cantor-Zahlen (falls korrekt) die Dichte am Set (2/3) ^ Log_3 (n) ist (was für eine seltsame Funktion) und ich stimme zu, dass die Dichte von log (n) / n zu stark ist.

Wenn dies die Obergrenze ist, gibt es einen Algorithmus, der dieses Problem in mindestens O (n * (3/2) ^ (log (n) / log (3))) Zeitkomplexität und O ((3/2) ^ ( log (n) / log (3))) Raumkomplexität. (Überprüfen Sie die Antwort der Justiz auf Algorithmus)

Dies ist immer noch weitaus besser als O (n ^ 2)

Diese Funktion ((3/2) ^ (log (n) / log (3))) sieht auf den ersten Blick wirklich wie n * log (n) aus.

Wie habe ich diese Formel bekommen?

Cantors Nummer auf Saite spielen.
Angenommen, die Länge der Zeichenfolge beträgt 3 ^ p == n
Bei jedem Schritt bei der Erzeugung der Cantor-Zeichenfolge behalten Sie 2/3 der vorherigen Anzahl von Einsen. Wenden Sie diese p-mal an.

Das bedeutet (n * ((2/3) ^ p)) -> (((3 ^ p)) * ((2/3) ^ p)) verbleibende und nach Vereinfachung 2 ^ p. Dies bedeutet 2 ^ p Einsen in 3 ^ p Strings -> (3/2) ^ p Einsen. Ersetze p = log (n) / log (3) und erhalte
((3/2) ^ (log (n) / log (3)))


False: Cantor-Set hat die Dichte n ^ log_3 (2).
SDCVVC

0

Wie wäre es mit einer einfachen O (n) -Lösung mit O (n ^ 2) -Raum? (Verwendet die Annahme, dass alle bitweisen Operatoren in O (1) arbeiten.)

Der Algorithmus arbeitet grundsätzlich in vier Schritten:

Stufe 1: Finden Sie für jedes Bit in Ihrer ursprünglichen Nummer heraus, wie weit diese entfernt sind, berücksichtigen Sie jedoch nur eine Richtung. (Ich habe alle Bits in Richtung des niedrigstwertigen Bits betrachtet.)

Stufe 2: Kehren Sie die Reihenfolge der Bits in der Eingabe um.

Stufe 3: Führen Sie Schritt 1 am umgekehrten Eingang erneut aus.

Stufe 4: Vergleichen Sie die Ergebnisse von Stufe 1 und Stufe 3. Wenn Bits über UND unter gleichem Abstand liegen, müssen wir einen Treffer erzielen.

Beachten Sie, dass kein Schritt im obigen Algorithmus länger dauert als O (n). ^ _ ^

Als zusätzlichen Vorteil findet dieser Algorithmus ALLE gleich beabstandeten von JEDER Zahl. Wenn Sie beispielsweise das Ergebnis "0x0005" erhalten, befinden sich BEIDE Einheiten 1 und 3 in gleichem Abstand

Ich habe nicht wirklich versucht, den folgenden Code zu optimieren, aber es ist kompilierbarer C # -Code, der zu funktionieren scheint.

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

Jemand wird wahrscheinlich kommentieren, dass für eine ausreichend große Anzahl bitweise Operationen in O (1) nicht ausgeführt werden können. Du hättest recht. Ich würde jedoch vermuten, dass jede Lösung, die Addition, Subtraktion, Multiplikation oder Division verwendet (was nicht durch Verschieben möglich ist), auch dieses Problem haben würde.


0

Unten ist eine Lösung. Hier und da könnte es einige kleine Fehler geben, aber die Idee ist richtig.

Bearbeiten: Es ist nicht n * log (n)

PSEUDO-CODE:

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

C # -Code:

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

Wie es funktioniert:

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
Ihr Algorithmus funktioniert, aber Sie müssen beweisen, dass er kleiner als O (n ^ 2) ist. Die triviale Analyse bringt Sie zu O (n ^ 2). Für jede 1 gehen Sie alle Einsen durch, die davor waren. Die Komplexitätsfunktion ist 1 + 2 + 3 + ... + (k / 2-1) = O (k ^ 2) [wobei k die Anzahl von 1s ist].
Anna

Ich habe geprüft und in der Tat ist das Worst-Case-Szenario ohne Lösung größer als O (n * log (n))
Niek H.

0

Offensichtlich müssen wir mindestens eine Reihe von Drillingen gleichzeitig überprüfen, also müssen wir die Überprüfungen irgendwie komprimieren. Ich habe einen Kandidatenalgorithmus, aber die Analyse der Zeitkomplexität liegt außerhalb meiner Fähigkeit * Zeitschwelle.

Erstellen Sie einen Baum, in dem jeder Knoten drei untergeordnete Knoten hat und jeder Knoten die Gesamtzahl der Einsen an seinen Blättern enthält. Erstellen Sie auch eine verknüpfte Liste über den Einsen. Weisen Sie jedem Knoten zulässige Kosten zu, die proportional zu dem Bereich sind, den er abdeckt. Solange die Zeit, die wir an jedem Knoten verbringen, innerhalb des Budgets liegt, haben wir einen O (n lg n) -Algorithmus.

- -

Beginnen Sie an der Wurzel. Wenn das Quadrat der Gesamtzahl der Einsen darunter unter den zulässigen Kosten liegt, wenden Sie den naiven Algorithmus an. Ansonsten auf seine Kinder zurückgreifen.

Jetzt sind wir entweder innerhalb des Budgets zurückgekehrt oder wir wissen, dass in einem der Kinder keine gültigen Drillinge vollständig enthalten sind. Daher müssen wir die Tripletts zwischen den Knoten überprüfen.

Jetzt wird es unglaublich chaotisch. Wir möchten im Wesentlichen auf die potenziellen Gruppen von Kindern zurückgreifen und gleichzeitig die Reichweite einschränken. Sobald der Bereich so eingeschränkt ist, dass der naive Algorithmus unter dem Budget ausgeführt wird, tun Sie dies. Viel Spaß beim Implementieren, denn ich garantiere, dass es langweilig wird. Es gibt wie ein Dutzend Fälle.

- -

Der Grund, warum ich denke, dass der Algorithmus funktionieren wird, ist, dass die Sequenzen ohne gültige Tripletts zwischen Bündeln von Einsen und vielen Nullen zu wechseln scheinen. Der nahe gelegene Suchraum wird effektiv aufgeteilt, und der Baum emuliert diese Aufteilung.

Die Laufzeit des Algorithmus ist überhaupt nicht offensichtlich. Es beruht auf den nicht trivialen Eigenschaften der Sequenz. Wenn die Einsen wirklich spärlich sind, funktioniert der naive Algorithmus unter Budget. Wenn die Einsen dicht sind, sollte sofort eine Übereinstimmung gefunden werden. Aber wenn die Dichte 'genau richtig' ist (z. B. in der Nähe von ~ n ^ 0,63, was Sie erreichen können, indem Sie alle Bits an Positionen ohne '2'-Ziffer in Basis 3 setzen), weiß ich nicht, ob es funktionieren wird. Sie müssten beweisen, dass der Aufteilungseffekt stark genug ist.


0

Keine theoretische Antwort hier, aber ich habe ein schnelles Java-Programm geschrieben, um das Laufzeitverhalten als Funktion von k und n zu untersuchen, wobei n die Gesamtbitlänge und k die Anzahl der Einsen ist. Ich bin mit einigen der Antwortenden zusammen, die sagen, dass der "reguläre" Algorithmus, der alle Paare von Bitpositionen überprüft und nach dem 3. Bit sucht, obwohl es im schlimmsten Fall O (k ^ 2) erfordern würde, in Realität, weil der schlimmste Fall spärliche Bitstrings benötigt, ist O (n ln n).

Wie auch immer, hier ist das Programm unten. Es ist ein Programm im Monte-Carlo-Stil, das eine große Anzahl von NTRIALS-Versuchen für die Konstante n ausführt und zufällig Bitsets für einen Bereich von k-Werten unter Verwendung von Bernoulli-Prozessen mit einer Einsen-Dichte generiert, die zwischen spezifizierbaren Grenzen begrenzt ist, und die Laufzeit aufzeichnet Um ein Triplett mit gleichmäßig verteilten zu finden oder nicht zu finden, wird die Zeit in Schritten NICHT in der CPU-Zeit gemessen. Ich habe es für n = 64, 256, 1024, 4096, 16384 * ausgeführt (läuft noch), zuerst einen Testlauf mit 500000 Versuchen, um festzustellen, welche k-Werte die längste Laufzeit benötigen, dann einen weiteren Test mit 5000000 Versuchen mit verengten Versuchen. Dichtefokus, um zu sehen, wie diese Werte aussehen. Die längsten Laufzeiten treten bei sehr geringer Dichte auf (z. B. für n = 4096 liegen die Laufzeitspitzen im Bereich von k = 16-64, mit einer sanften Spitze für die mittlere Laufzeit bei 4212 Schritten @ k = 31, Die maximale Laufzeit erreichte einen Spitzenwert von 5101 Schritten (k = 58). Es sieht so aus, als würde es extrem große Werte von N erfordern, bis der O (k ^ 2) -Schritt im ungünstigsten Fall größer wird als der O (n) -Schritt, bei dem Sie den Bitstring scannen, um die Positionsindizes der 1 zu finden.

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

Ich habe Probleme mit den Worst-Case-Szenarien mit Millionen von Ziffern. Das Fuzzing von /dev/urandomergibt im Wesentlichen O (n), aber ich weiß, dass der schlimmste Fall schlimmer ist. Ich kann nur nicht sagen, wie viel schlimmer. Für kleine Unternehmen nist es trivial, Inputs in der Nähe zu finden 3*n*log(n), aber es ist überraschend schwierig, diese von einer anderen Wachstumsordnung für dieses spezielle Problem zu unterscheiden.

Kann jemand, der an Worst-Case-Eingaben gearbeitet hat, eine Zeichenfolge mit einer Länge von mehr als beispielsweise einhunderttausend generieren?


Wie ich in meiner Antwort ausgeführt habe, ist es einfach, schlechte (wenn auch nicht im schlimmsten Fall) Zeichenfolgen mit einer beliebigen Anzahl von Ziffern zu generieren: Setzen Sie 1s genau an die Positionen p, die in ihrer ternären Darstellung keine "1" enthalten (dh at Positionen 2, 6, 8, 18, 20, 24, 26, 54, 56, 60 ...: siehe Formeln unter research.att.com/~njas/sequences/…). Für 3 ^ 13 ≈ 1 Million hat dies 2 ^ 13 ≈ 8000 1s. Die Laufzeit für solche Zeichenfolgen beträgt ≈ n ^ (1,26) - was für ein so kleines n möglicherweise immer noch schwer von O (n log n) zu unterscheiden ist. Probieren Sie es aus und sehen Sie.
ShreevatsaR


-3

Könnte dies eine Lösung sein? Ich bin mir nicht sicher, ob es O (nlogn) ist, aber meiner Meinung nach ist es besser als O (n²), weil der einzige Weg, kein Tripel zu finden, eine Primzahlverteilung wäre.

Es gibt Raum für Verbesserungen, die zweite gefundene 1 könnte die nächste erste sein 1. Auch keine Fehlerprüfung.

#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
Technisch ist dies O (n ^ 2). Im Durchschnitt iteriert die innere Schleife jedes Mal, wenn sie ausgeführt wird, über die Hälfte von n. Es könnte also als O (n * (n / 2)) geschrieben werden, und das kann zu O (n ^ 2) vereinfacht werden
Robert Parker

Hm, sieht so aus, als hättest du recht. Dies ist kein einfaches Problem, nur um alle 1 zu finden, braucht O (n), nicht viel Raum für eine weitere Suche / einen weiteren Vergleich mit der O (logn) -Komplexität.
DaClown

-3

Ich denke, dieser Algorithmus hat O (n log n) Komplexität (C ++, DevStudio 2k5). Jetzt weiß ich nicht genau, wie ein Algorithmus analysiert werden muss, um seine Komplexität zu bestimmen. Daher habe ich dem Code einige Informationen zum Sammeln von Metriken hinzugefügt. Der Code zählt die Anzahl der Tests, die an der Folge von Einsen und Nullen für eine bestimmte Eingabe durchgeführt wurden (hoffentlich habe ich keine Bälle aus dem Algorithmus gemacht). Wir können die tatsächliche Anzahl der Tests mit dem O-Wert vergleichen und feststellen, ob eine Korrelation besteht.

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

Dieses Programm gibt die Anzahl der Tests für jede Zeichenfolgenlänge mit bis zu 32 Zeichen aus. Hier sind die Ergebnisse:

 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

Ich habe auch die 'n log n' Werte hinzugefügt. Zeichnen Sie diese mit dem Grafikwerkzeug Ihrer Wahl, um eine Korrelation zwischen den beiden Ergebnissen festzustellen. Erstreckt sich diese Analyse auf alle Werte von n? Ich weiß es nicht.


Es ist keine perfekte Korrelation, da stimme ich zu. Die Kurve ist jedoch näher an n log n als an n ^ 2.
Skizz

3
Versuchen Sie, die Eingangsgröße um eine Million oder mehr zu erhöhen. Bei kleinen Eingaben ähnelt die Kurve häufig den Kurven von Algorithmen, die offensichtlich besser sind, wenn die Eingabegröße aufgepumpt wird.
Nick Larsen

Eine doppelte for-Schleife, wobei die innere durch die äußere begrenzt ist, ergibt eine dreieckige Form, deren Komplexität immer noch O (n ^ 2) ist. Stellen Sie sich alles (i, j) so vor, dass i in [0, n] und j in [0, n-2 * i] ein Dreieck hat und die Fläche eines Dreiecks eine quadratische Tendenz hat.
Matthieu M.

Um genau zu sein, Tests = (n ^ 2-2n) / 4 für gerade n; offensichtlich quadratisch.
Opa
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.