Update: Der hier beschriebene erste Algorithmus wird durch die zweite Antwort von Armin Rigo überholt , die viel einfacher und effizienter ist. Beide Methoden haben jedoch einen Nachteil. Sie benötigen viele Stunden, um das Ergebnis für eine Million Ganzzahlen zu finden. Also habe ich zwei weitere Varianten ausprobiert (siehe zweite Hälfte dieser Antwort), bei denen angenommen wird, dass der Bereich der Eingabe-Ganzzahlen begrenzt ist. Eine solche Einschränkung ermöglicht viel schnellere Algorithmen. Außerdem habe ich versucht, den Code von Armin Rigo zu optimieren. Siehe meine Benchmarking-Ergebnisse am Ende.
Hier ist eine Idee eines Algorithmus, der einen O (N) -Speicher verwendet. Die zeitliche Komplexität beträgt O (N 2 log N), kann jedoch auf O (N 2 ) verringert werden .
Der Algorithmus verwendet die folgenden Datenstrukturen:
prev
: Array von Indizes, die auf das vorherige Element der (möglicherweise unvollständigen) Teilsequenz verweisen.
hash
: Hashmap mit Schlüssel = Differenz zwischen aufeinanderfolgenden Paaren in Teilsequenz und Wert = zwei weitere Hashmaps. Für diese anderen Hashmaps: key = Start- / Endindex der Teilsequenz, value = Paar von (Teilsequenzlänge, End- / Startindex der Teilsequenz).
pq
: Prioritätswarteschlange für alle möglichen "Differenz" -Werte für in prev
und gespeicherte Teilsequenzen hash
.
Algorithmus:
prev
Mit Indizes initialisieren i-1
. Aktualisieren hash
und pq
registrieren Sie alle (unvollständigen) Teilsequenzen, die in diesem Schritt gefunden wurden, und ihre "Unterschiede".
- Holen Sie sich den kleinsten "Unterschied" von (und entfernen Sie ihn)
pq
. Holen Sie sich den entsprechenden Datensatz hash
und scannen Sie eine der Hash-Maps der zweiten Ebene. Zu diesem Zeitpunkt sind alle Teilsequenzen mit gegebener "Differenz" vollständig. Wenn die Hash-Map der zweiten Ebene eine bessere Teilsequenzlänge als bisher enthält, aktualisieren Sie das beste Ergebnis.
- Im Array
prev
: Für jedes Element einer in Schritt 2 gefundenen Sequenz den Index dekrementieren und aktualisieren hash
und möglicherweise pq
. Während der Aktualisierung hash
können wir eine der folgenden Operationen ausführen: Hinzufügen einer neuen Teilsequenz der Länge 1 oder Erhöhen einer vorhandenen Teilsequenz um 1 oder Zusammenführen zweier vorhandener Teilsequenzen.
- Entfernen Sie den in Schritt 2 gefundenen Hash-Map-Datensatz.
- Fahren Sie mit Schritt 2 fort, solange dieser
pq
nicht leer ist.
Dieser Algorithmus aktualisiert jeweils O (N) Elemente von prev
O (N) mal. Und für jedes dieser Updates muss möglicherweise ein neuer "Unterschied" hinzugefügt werden pq
. All dies bedeutet Zeitkomplexität von O (N 2 log N), wenn wir eine einfache Heap-Implementierung für verwenden pq
. Um es auf O (N 2 ) zu verringern, verwenden wir möglicherweise erweiterte Prioritätswarteschlangenimplementierungen. Einige der Möglichkeiten sind auf dieser Seite aufgeführt: Prioritätswarteschlangen .
Siehe entsprechenden Python-Code auf Ideone . Dieser Code erlaubt keine doppelten Elemente in der Liste. Es ist möglich, dies zu beheben, aber es wäre trotzdem eine gute Optimierung, Duplikate zu entfernen (und die längste Teilsequenz nach Duplikaten separat zu finden).
Und der gleiche Code nach ein wenig Optimierung . Hier wird die Suche beendet, sobald die Teilsequenzlänge multipliziert mit einer möglichen Teilsequenzdifferenz den Quelllistenbereich überschreitet.
Der Code von Armin Rigo ist einfach und ziemlich effizient. In einigen Fällen werden jedoch einige zusätzliche Berechnungen durchgeführt, die vermieden werden können. Die Suche kann beendet werden, sobald die Teilsequenzlänge multipliziert mit der möglichen Teilsequenzdifferenz den Quelllistenbereich überschreitet:
def findLESS(A):
Aset = set(A)
lmax = 2
d = 1
minStep = 0
while (lmax - 1) * minStep <= A[-1] - A[0]:
minStep = A[-1] - A[0] + 1
for j, b in enumerate(A):
if j+d < len(A):
a = A[j+d]
step = a - b
minStep = min(minStep, step)
if a + step in Aset and b - step not in Aset:
c = a + step
count = 3
while c + step in Aset:
c += step
count += 1
if count > lmax:
lmax = count
d += 1
return lmax
print(findLESS([1, 4, 5, 7, 8, 12]))
Wenn der Bereich von ganzen Zahlen in den Quelldaten (M) klein ist, ist ein einfacher Algorithmus mit O (M 2 ) -Zeit und O (M) -Raum möglich:
def findLESS(src):
r = [False for i in range(src[-1]+1)]
for x in src:
r[x] = True
d = 1
best = 1
while best * d < len(r):
for s in range(d):
l = 0
for i in range(s, len(r), d):
if r[i]:
l += 1
best = max(best, l)
else:
l = 0
d += 1
return best
print(findLESS([1, 4, 5, 7, 8, 12]))
Es ähnelt der ersten Methode von Armin Rigo, verwendet jedoch keine dynamischen Datenstrukturen. Ich nehme an, Quelldaten haben keine Duplikate. Und (um den Code einfach zu halten) ich nehme auch an, dass der minimale Eingabewert nicht negativ ist und nahe Null liegt.
Der vorherige Algorithmus kann verbessert werden, wenn anstelle des Arrays von Booleschen Werten eine Bitset-Datenstruktur und bitweise Operationen verwendet werden, um Daten parallel zu verarbeiten. Der unten gezeigte Code implementiert Bitset als integrierte Python-Ganzzahl. Es hat die gleichen Annahmen: keine Duplikate, minimaler Eingabewert ist nicht negativ und nahe Null. Die zeitliche Komplexität ist O (M 2 * log L), wobei L die Länge der optimalen Teilsequenz ist, die räumliche Komplexität ist O (M):
def findLESS(src):
r = 0
for x in src:
r |= 1 << x
d = 1
best = 1
while best * d < src[-1] + 1:
c = best
rr = r
while c & (c-1):
cc = c & -c
rr &= rr >> (cc * d)
c &= c-1
while c != 1:
c = c >> 1
rr &= rr >> (c * d)
rr &= rr >> d
while rr:
rr &= rr >> d
best += 1
d += 1
return best
Benchmarks:
Eingabedaten (ca. 100000 Ganzzahlen) werden folgendermaßen generiert:
random.seed(42)
s = sorted(list(set([random.randint(0,200000) for r in xrange(140000)])))
Und für die schnellsten Algorithmen habe ich auch die folgenden Daten verwendet (ungefähr 1000000 Ganzzahlen):
s = sorted(list(set([random.randint(0,2000000) for r in xrange(1400000)])))
Alle Ergebnisse zeigen die Zeit in Sekunden:
Size: 100000 1000000
Second answer by Armin Rigo: 634 ?
By Armin Rigo, optimized: 64 >5000
O(M^2) algorithm: 53 2940
O(M^2*L) algorithm: 7 711