76 votes

Plus longue sous-séquence également espacés

J'ai un million de nombres entiers dans l'ordre et je voudrais trouver la plus longue sous-suite où la différence entre deux paires est égal. Par exemple

1, 4, 5, 7, 8, 12

a une sous-suite

   4,       8, 12

Ma méthode naïve est gourmande et vérifie juste combien vous pouvez étendre une sous-suite de chaque point. Cela prend O(n²) temps par point, il me semble.

Est-il un moyen plus rapide pour résoudre ce problème?

La mise à jour. Je vais tester le code donné dans les réponses dès que possible (merci). Cependant, il est déjà clair que l'utilisation de n^2 mémoire ne fonctionnera pas. Jusqu'à présent il n'y a pas de code qui se termine avec l'entrée en tant que [random.randint(0,100000) for r in xrange(200000)] .

Les horaires. J'ai testé avec la suite de l'entrée de données sur mon système 32 bits.

a= [random.randint(0,10000) for r in xrange(20000)] 
a.sort()
  • La dynamique de la méthode de programmation de ZelluX utilise 1,6 G de RAM et prend 2 minutes et 14 secondes. Avec pypy, il faut seulement 9 secondes! Cependant il se bloque avec une erreur de mémoire sur les grandes entrées.
  • Le O(nd) méthode d'Armin a 9 secondes avec pypy, mais seulement 20 MO de RAM. Bien sûr, ce serait bien pire si la plage étaient beaucoup plus grands. La faible utilisation de la mémoire j'ai pu aussi tester avec a= [random.randint(0,100000) pour r in xrange(200000)], mais il n'a pas fini dans les quelques minutes qui me l'a donné avec pypy.

Afin d'être en mesure de tester la méthode de Kluev je rediffusé avec

a= [random.randint(0,40000) for r in xrange(28000)] 
a = list(set(a))
a.sort()

de faire une liste de longueur à peu près 20000. Tous les horaires avec pypy

  • ZelluX, 9 secondes
  • Kluev, 20 secondes
  • Armin, 52 secondes

Il semble que si la ZelluX méthode pourrait être faite linéaire de l'espace, il serait le gagnant clair.

19voto

Armin Rigo Points 4754

Nous avons peut-être une solution en O(n*m) dans le temps avec très peu de mémoire besoins, en adaptant la vôtre. Ici, n est le nombre d'éléments dans la séquence d'entrée de nombres et m correspond à la plage, c'est à dire le nombre le plus grand moins le plus bas.

Appel A la séquence d'entrée tous les numéros (et utiliser un précalculées set() pour répondre à la constante de temps de la question "est ce numéro?"). Appel d l' étape de la sous-suite, nous sommes à la recherche pour le (la différence entre les deux nombres de cette sous-suite). Pour chaque valeur de d, procédez de la manière suivante analyse linéaire sur la saisie de tous les nombres: pour chaque nombre n de A, dans l'ordre croissant, si le nombre n'était pas déjà vu, regarder vers l'avant pour la longueur de la séquence de départ à n avec une étape d. Ensuite, marquer tous les éléments de la séquence, comme déjà vu, afin de nous éviter de chercher à partir, pour la ré. De ce fait, la complexité est juste O(n) pour chaque valeur de d.

A = [1, 4, 5, 7, 8, 12]    # in sorted order
Aset = set(A)

for d in range(1, 12):
    already_seen = set()
    for a in A:
        if a not in already_seen:
            b = a
            count = 1
            while b + d in Aset:
                b += d
                count += 1
                already_seen.add(b)
            print "found %d items in %d .. %d" % (count, a, b)
            # collect here the largest 'count'

Mises à jour:

  • Cette solution pourrait être assez bon si vous êtes seulement intéressé par les valeurs de d sont relativement faibles; par exemple, si l'obtention du meilleur résultat pour d <= 1000 serait assez bon. Ensuite, la complexité descend O(n*1000). Cela rend l'algorithme approximatif, mais en fait praticable pour n=1000000. (Mesurée à 400-500 secondes avec Disponible, de 80 à 90 secondes avec PyPy, avec un sous-ensemble aléatoire de nombres entre 0 et 10'000'000.)

  • Si vous voulez toujours à la recherche pour l'ensemble de la gamme, et si le cas le plus courant est que de longues séquences existent, une amélioration notable de l'est pour s'arrêter dès que d est trop grande pour une même séquence plus longue à trouver.

12voto

ZelluX Points 15836

Mise à JOUR: j'ai trouvé un papier sur ce problème, vous pouvez le télécharger ici.

Voici une solution basée sur la programmation dynamique. Il faut O(n^2) le temps de la complexité et de O(n^2) l'espace de la complexité, et de ne pas utiliser le hachage.

Nous supposons que tous les numéros sont enregistrés dans la gamme a dans l'ordre croissant, et n enregistre sa longueur. Tableau 2D l[i][j] définit la longueur de la plus longue équidistants sous-suite en terminant par a[i] et a[j], et l[j][k] = l[i][j] + 1 si a[j] - a[i] = a[k] - a[j] (i < j < k).

lmax = 2
l = [[2 for i in xrange(n)] for j in xrange(n)]
for mid in xrange(n - 1):
    prev = mid - 1
    succ = mid + 1
    while (prev >= 0 and succ < n):
        if a[prev] + a[succ] < a[mid] * 2:
            succ += 1
        elif a[prev] + a[succ] > a[mid] * 2:
            prev -= 1
        else:
            l[mid][succ] = l[prev][mid] + 1
            lmax = max(lmax, l[mid][succ])
            prev -= 1
            succ += 1

print lmax

11voto

Evgeny Kluev Points 16685

Mise à jour: Premier algorithme décrit ici est rendu obsolète par Armin Rigo, la seconde réponse, qui est beaucoup plus simple et plus efficace. Mais ces deux méthodes ont un inconvénient. Ils ont besoin de plusieurs heures pour trouver le résultat pour un million de nombres entiers. J'ai donc essayé les deux variantes (voir la deuxième moitié de cette réponse) où la portée de la saisie des nombres entiers est supposé être limitée. Cette limitation permet de bien des algorithmes plus rapides. Aussi, j'ai essayé d'optimiser Armin Rigo du code. Voir mes résultats de l'analyse comparative à la fin.


Voici une idée de l'algorithme à l'aide de O(N) de la mémoire. Le temps de la complexité est O(N2 log N), mais peut être réduite à O(N2).

L'algorithme utilise les structures de données suivantes:

  1. prev: tableau d'indices pointant vers l'élément précédent (éventuellement incomplet) sous-suite.
  2. hash: table de hachage avec clé = différence entre deux paires de sous-suite et valeur = deux autres hashmaps. Pour ces autres hashmaps: clé = de début/fin de l'index de la sous-suite, valeur = paire de (la longueur, la fin de/indice de départ de la sous-suite).
  3. pq: file d'attente de priorité pour tous les possible de "différence" des valeurs pour les séquences stockées dans prev et hash.

Algorithme:

  1. Initialiser prev avec index i-1. Mise à jour de hash et pq d'enregistrer tous (incomplète) de sous-séquences trouvées sur cette étape et de leurs "différences".
  2. Obtenir (et supprimer) la plus petite "différence" de l' pq. Obtenez de l'enregistrement correspondant de hash de numérisation et de celui de de deuxième niveau de hachage cartes. À ce moment, toutes les sous-séquences avec la "différence" sont complètes. Si le deuxième niveau de hachage carte contient la longueur de mieux que trouvé à ce jour, de mettre à jour le meilleur résultat.
  3. Dans le tableau prev: pour chaque élément d'une séquence à l'étape #2, décrémenter index et mise à jour de hash et, éventuellement, pq. Alors que la mise à jour de hash, nous pourrions effectuer l'une des opérations suivantes: ajouter une nouvelle sous-suite d'une longueur de 1, ou de pousser certains la par 1, ou de fusionner les deux sous-séquences.
  4. Supprimer de hachage de la carte d'enregistrement trouvé à l'étape #2.
  5. Continuer à partir de l'étape #2 en pq n'est pas vide.

Cet algorithme mises à jour de O(N) éléments d' prev O(N) fois chacun. Et chacun de ces mises à jour peuvent nécessiter d'ajouter un nouveau "différence" pq. Tout cela signifie que le temps de la complexité de O(N2 log N) si nous utilisons de simples tas de mise en œuvre pour l' pq. Pour la réduire à O(N2) on pourrait utiliser plus avancés file d'attente de priorité des implémentations. Certaines de ces possibilités sont répertoriés sur cette page: Files d'attente de Priorité.

Voir le code Python sur Ideone. Ce code ne permet pas de dupliquer des éléments dans la liste. Il est possible de remédier à ce problème, mais ce serait une bonne optimisation de toute façon à supprimer les doublons (et à trouver la plus longue sous-suite-delà de doublons séparément).

Et , du même code, après un peu d'optimisation. Ici, la recherche est terminée dès que la longueur multipliée par d'éventuels sous-suite "différence" dépasse liste des sources de gamme.


Armin Rigo du code est simple et assez efficace. Mais dans certains cas cela fait quelques calculs supplémentaires qui peuvent être évités. La recherche peut être résilié dès que la longueur multipliée par d'éventuels sous-suite "différence" dépasse liste des sources de gamme:

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

Si la fourchette de nombres entiers dans la source de données (M) est faible, un algorithme simple est possible avec O(M2) en temps et O(M) de l'espace:

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

Il est similaire à la première méthode par Armin Rigo, mais il n'utilise pas toutes les structures de données dynamiques. Je suppose que la source de données n'a pas de doublons. Et (pour simplifier le code), je suppose que le minimum de la valeur d'entrée est non-négative et proche de zéro.


Algorithme précédent peut être améliorée si au lieu d'un tableau de booléens, nous utilisons un bitset structure de données et des opérations bit à bit de données de processus en parallèle. Le code ci-dessous met en œuvre bitset qu'un Python entier. Il a les mêmes hypothèses: pas de doublons, le minimum de la valeur d'entrée est non-négative et proche de zéro. Le temps de la complexité est O(M2 * log L) où L est la longueur optimale des sous-suite, l'espace, la complexité est 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

Repères:

Les données d'entrée (environ 100000 entiers) est généré de cette façon:

random.seed(42)
s = sorted(list(set([random.randint(0,200000) for r in xrange(140000)])))

Et pour le plus rapide des algorithmes j'ai aussi utilisé les données suivantes (environ 1000000 entiers):

s = sorted(list(set([random.randint(0,2000000) for r in xrange(1400000)])))

Tous les résultats montrent de temps en secondes:

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

3voto

Roman Pekar Points 31863

Algorithme

  • Boucle principale traversant la liste
  • Si le nombre trouvé dans précalculer liste, ils appartiennent à toutes les séquences qui sont dans cette liste, de recalculer toutes les séquences avec count + 1
  • Supprimer tout calculé pour l'élément courant
  • Recalculer de nouvelles séquences où le premier élément est à partir de la gamme de 0 à actuel, et la deuxième est l'élément en cours de parcours (en fait, pas de 0 de courant, on peut utiliser le fait que l'élément nouveau ne devrait pas être plus que max(a) et de la nouvelle liste doit avoir la possibilité de devenir plus que déjà trouvé un)

Donc, pour liste [1, 2, 4, 5, 7] sortie (c'est un peu désordonné, essayez le code vous-même et voir)

  • l'indice 0, élément 1:
    • si 1 dans precalc? Sans ne rien faire
    • Ne rien faire
  • indice 1, élément 2:
    • si 2 dans precalc? Sans ne rien faire
    • vérifier si 3 = 1 + (2 - 1) * 2 dans notre jeu? Sans ne rien faire
  • indice 2, élément 4:
    • si 4 dans precalc? Sans ne rien faire
      • vérifier si 6 = 2 + (4 - 2) * 2 dans notre jeu? Pas de
      • vérifier si 7 = 1 + (4 - 1) * 2 dans notre jeu? Oui - ajouter un nouvel élément {7: {3: {'count': 2, 'start': 1}}} 7 - élément de la liste, 3 est l'étape.
  • indice 3, de l'élément 5:
    • si 5 dans precalc? Sans ne rien faire
      • ne pas vérifier l' 4 car 6 = 4 + (5 - 4) * 2 est inférieur à celui calculé élément 7
      • vérifier si 8 = 2 + (5 - 2) * 2 dans notre jeu? Pas de
      • case 10 = 2 + (5 - 1) * 2 - plus que max(a) == 7
  • indice 4, l'élément 7:
    • si 7 en precalc? Oui - la mise en résultat
      • ne pas vérifier l' 5 car 9 = 5 + (7 - 5) * 2 est plus que max(a) == 7

résultat = (3, {"count": 3, "start": 1}) # étape 3, comptez 3, 1, de le transformer en séquence

La complexité

Il ne devrait pas être plus de O(N^2), et je pense que c'est moins en raison de la résiliation anticipée de la recherche de nouveaux sequencies, je vais essayer de fournir une analyse détaillée plus tard

Code

def add_precalc(precalc, start, step, count, res, N):
    if step == 0: return True
    if start + step * res[1]["count"] > N: return False

    x = start + step * count
    if x > N or x < 0: return False

    if precalc[x] is None: return True

    if step not in precalc[x]:
        precalc[x][step] = {"start":start, "count":count}

    return True

def work(a):
    precalc = [None] * (max(a) + 1)
    for x in a: precalc[x] = {}
    N, m = max(a), 0
    ind = {x:i for i, x in enumerate(a)}

    res = (0, {"start":0, "count":0})
    for i, x in enumerate(a):
        for el in precalc[x].iteritems():
            el[1]["count"] += 1
            if el[1]["count"] > res[1]["count"]: res = el
            add_precalc(precalc, el[1]["start"], el[0], el[1]["count"], res, N)
            t = el[1]["start"] + el[0] * el[1]["count"]
            if t in ind and ind[t] > m:
                m = ind[t]
        precalc[x] = None

        for y in a[i - m - 1::-1]:
            if not add_precalc(precalc, y, x - y, 2, res, N): break

    return [x * res[0] + res[1]["start"] for x in range(res[1]["count"])]

3voto

Armin Rigo Points 4754

Voici une autre réponse, en temps et en heure O(n^2) et sans aucun notable des exigences de la mémoire au-delà de tourner la liste dans un ensemble.

L'idée est assez naïve: à l'instar de l'affiche originale, elle est gourmande et vérifie juste combien vous pouvez étendre une sous-suite de chaque paire de points --- cependant, en vérifiant d'abord que nous sommes au début d'une sous-suite. En d'autres termes, à partir de points de a et b - vous de vérifier dans quelle mesure vous pouvez étendre à l' b + (b-a), b + 2*(b-a), ... mais seulement si a - (b-a) n'est pas déjà dans l'ensemble de tous les points. Si c'est le cas, vous avez déjà vu le même sous-suite.

Le truc, c'est de se convaincre que ce simple optimisation suffit de baisser la complexité O(n^2) de l'original de l' O(n^3). Cela est laissé en exercice au lecteur :-) Le temps est en compétition avec d'autres O(n^2) solutions ici.

A = [1, 4, 5, 7, 8, 12]    # in sorted order
Aset = set(A)

lmax = 2
for j, b in enumerate(A):
    for i in range(j):
        a = A[i]
        step = b - a
        if b + step in Aset and a - step not in Aset:
            c = b + step
            count = 3
            while c + step in Aset:
                c += step
                count += 1
            #print "found %d items in %d .. %d" % (count, a, c)
            if count > lmax:
                lmax = count

print lmax

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X