Comment vérifier si deux chaînes sont des permutations l'une de l'autre en utilisant l'espace supplémentaire O (1)?

13

Étant donné deux chaînes, comment pouvez-vous vérifier si elles sont une permutation l'une de l'autre en utilisant l'espace O (1)? La modification des chaînes n'est en aucun cas autorisée.
Remarque: espace O (1) par rapport à la longueur de la chaîne ET à la taille de l'alphabet.

Anonyme
la source
3
Qu'est-ce que tu penses? Qu'avez-vous essayé et où êtes-vous resté coincé? Les chaînes sont-elles sur un alphabet de taille constante? Avez-vous essayé de calculer leurs histogrammes?
Yuval Filmus
@YuvalFilmus il devrait y avoir un espace O (1) à la fois pour la longueur de la chaîne et la taille de l'alphabet
Anonyme
Cela semble clairement impossible. Tout algorithme nécessitera un espace supplémentaire pour stocker au moins une position dans une chaîne ou un seul caractère. Aucune de ces choses n'est O (1).
David Schwartz
@DavidSchwartz - comment? O (1) signifie constant, pas un seul bute. Peu importe la longueur de la chaîne, sa position est un nombre.
Davor
Cela dépend du modèle de la machine, évidemment pas de problème dans les modèles uniformes. Dans un modèle de coût logarithmique, le stockage de l'indice concerne O(log n)des chaînes de longueur n qui ne sont ni constantes par la longueur ni par la taille de l'alphabet. Lorsque les chaînes peuvent être temporairement modifiées, je pense qu'il existe une solution avec un alphabet accru qui est linéaire dans la taille de l'alphabet mais constant dans la longueur des chaînes dans un modèle logarithmique.
kap

Réponses:

7

L'approche naïve consisterait à créer des histogrammes des deux chaînes et à vérifier si elles sont identiques. Comme nous ne sommes pas autorisés à stocker une telle structure de données (dont la taille serait linéaire à la taille de l'alphabet) qui pourrait être calculée en une seule passe, nous devons compter les occurrences de chaque symbole possible après l'autre:

function count(letter, string)
    var count := 0
    foreach element in string
        if letter = element
            count++
    return count

function samePermutation(stringA, stringB)
    foreach s in alphabet
        if count(s, stringA) != count(s, stringB)
            return false
    return true

Cela suppose bien sûr que les nombres et les indices d'itérateur sont des entiers de taille constante, au lieu de dépendre de la longueur des chaînes.

Bergi
la source
À titre d'optimisation, vous pouvez parcourir un tableau et calculer uniquement les histogrammes des lettres que vous rencontrez. De cette façon, la complexité devient indépendante de la taille de l'alphabet.
Yuval Filmus
Pour développer le commentaire @YuvalFilmus, vous devez également 1) vérifier que les longueurs de chaîne sont identiques ou 2) itérer sur les deux chaînes d'entrée. Vous en avez besoin car il est possible que certaines lettres dans l'une ne soient pas dans l'autre. L'option 1 devrait avoir moins de calculs.
BurnsBA
@YuvalFilmus Je voulais éviter cela car cela signifierait une complexité temporelle quadratique, je m'attendrais à ce que l'alphabet soit plus petit que la taille moyenne des chaînes. Pour les petites chaînes et un alphabet ordonné, j'envisagerais de calculer le prochain plus petit symbole actuel avec le nombre dans la boucle intérieure, afin que l'on puisse ignorer quelques itérations de la boucle alphabétique - avec une complexité de O(n * min(n, |Σ|)). Hm, maintenant que j'y pense, cela ressemble beaucoup à la solution "autorisé à répéter" de votre réponse, n'est-ce pas?
Bergi
countn'est pas O(1)(c'est-à-dire qu'il peut déborder)
reinierpost
1
@Eternalcode Je n'ai jamais dit que countc'était un int:-) Oui, ça ne marcherait pas, mais en Java ça ne peut pas arriver de toute façon
Bergi
12

Notons les tableaux par et supposons qu'ils sont de longueur n .UNE,Bn

Supposons d'abord que les valeurs de chaque tableau soient distinctes. Voici un algorithme qui utilise l' espace :O(1)

  1. Calculez les valeurs minimales des deux tableaux et vérifiez qu'elles sont identiques.

  2. Calculez les deuxièmes valeurs minimales des deux tableaux et vérifiez qu'elles sont identiques.

  3. Etc.

Le calcul de la valeur minimale d'un tableau utilise clairement l' espace . Étant donné le k ème plus petit élément, nous pouvons trouver le ( k + 1 ) st le plus petit élément en trouvant la valeur minimale plus grande que le kO(1)k(k+1)k ème plus petit élément (ici, nous utilisons le fait que tous les éléments sont distincts).

Lorsque les éléments sont autorisés à se répéter, nous modifions l'algorithme comme suit:

  1. Calculez les valeurs minimales des deux tableaux, comptez combien de fois chacun apparaît et vérifiez les m A , 1 = m B , 1mA,1,mB,1mA,1=mB,1 et que les comptes sont identiques.

  2. Calculez les valeurs minimales supérieures à m A , 1 , m B , 1 dans les deux tableaux (respectivement) et comptez combien de fois chacune apparaît. Vérifiez que m A , 2 = m B , 2 et que les nombres sont identiques.mUNE,2,mB,2mUNE,1,mB,1mUNE,2=mB,2

  3. Etc.

Yuval Filmus
la source
1
Cette approche serait-elle car elle semble être le seul moyen de trouver l'élémentmindans l'espace O ( 1 ) et l'accès en lecture seule au tableau est d'itérer sur tous les éléments? O(n2)O(1)
ryan
4
Cela nécessite un ordre sur l'alphabet, bien qu'il soit facile de changer l'algorithme pour ne pas l'exiger. Cependant, dans le cas "a des doublons", cela nécessite espace n ) , pas O ( 1 ) . Le comptage prend de la place. O(lgn)O(1)
Derek Elkins a quitté le SE
7
Le comptage a besoin d'espace (logarithmique), mais - selon cette définition de l'utilisation de l'espace - il en va de même pour l'itération sur le tableau. Ainsi, au sens strict de l'utilisation de l'espace, il n'y a aucun moyen de le faire dans un espace constant.
Daniel Jour
4
@DanielJour, cela dépend du modèle de coût que vous utilisez. Sous un coût uniforme, cela est possible dans un espace constant.
ryan
7
Si vous ne disposez que d'un nombre constant de bits, vous ne pouvez gérer que des alphabets de taille constante (cela découle de la théorie des langues régulières).
Yuval Filmus
2

Définissez une fonction f (c) qui mappe un caractère c à un nombre premier unique (a = 2, b = 3, c = 5, etc.).

set checksum = 1
set count = 0 <-- this is probably not even necessary, but it's another level of check
for character c in string 1
    checksum = checksum * f(c)
    count = count + 1
for character c in string 2
    checksum = checksum / f(c)
    count = count = 1

permutation = count == 0 and checksum == 1

Le simple fait de déclarer que vous pouvez utiliser une fonction de mappage de nombres premiers est un peu vague, et très probablement où un problème surviendrait en gardant l' espace .O(1)

Alex Stasse
la source
Avec une borne sur l'alphabet, devrait utiliser l' espace O ( 1 ) , sinon je pense que ce ne serait pas un espace constant. De plus, si vous le calculiez dans l' espace O ( 1 ) , il serait extrêmement inefficace sur la base des résultats actuels . Pourtant, +1 pour l'approche de primalité. F(c)O(1)O(1)
ryan
Un autre problème que j'ai réalisé après la publication est que la somme de contrôle va être un nombre gigantesque pour les chaînes de grande taille, dans la mesure où en soi, il pourrait violer l'exigence d'espace O (1). Cela peut être résolu en utilisant des flottants et en multipliant par un caractère sur une chaîne, puis en divisant sur l'autre, puis en disant simplement que la somme de contrôle doit être proche de 1. Les chaînes doivent être vraiment gigantesques pour que l'erreur en virgule flottante soit un problème.
Alex Stasse
4
Ces réponses sont la raison pour laquelle nous devons faire attention à notre modèle de calcul. Le modèle habituel que nous utilisons pour analyser les algorithmes compte la mémoire en unités de mots machine , qui ont des bits de taille . Vous ne pouvez donc pas faire le calcul en nombres entiers. Si vous passez en virgule flottante, votre algorithme peut échouer même lorsque les deux chaînes sont des permutations l'une de l'autre, et inversement ne donnera pas nécessairement la bonne réponse quand elles ne le sont pas. O(Journaln)
Yuval Filmus
4
Cela n'utilise pas d'espace constant. Même pour un alphabet fixe, la taille de la somme de contrôle entière sera de bits pour les entrées de longueur n .Θ(n)n
David Richerby
0

Vous pouvez le faire est O(nlogn). Triez les deux chaînes et comparez-les index par index. S'ils diffèrent quelque part, ce ne sont pas des permutations les uns des autres.

Pour une O(n)solution, le hachage pourrait être utilisé. Cette fonction de hachage fonctionnerait, et epour toute lettre serait sa valeur ascii. Si les deux hachages des chaînes diffèrent, ce ne sont pas des permutations l'une de l'autre.

La fonction de hachage dans le lien:

Un candidat potentiel pourrait être celui-ci. Fixez un entier impair R. Pour chaque élément e que vous souhaitez hacher, calculez le facteur (R + 2 * e). Calculez ensuite le produit de tous ces facteurs. Enfin, divisez le produit par 2 pour obtenir le hachage.

Le facteur 2 dans (R + 2e) garantit que tous les facteurs sont impairs, évitant ainsi que le produit ne devienne jamais 0. La division par 2 à la fin est parce que le produit sera toujours impair, donc la division supprime simplement un bit constant .

Par exemple, je choisis R = 1779033703. C'est un choix arbitraire, faire quelques expériences devrait montrer si un R donné est bon ou mauvais. Supposons que vos valeurs soient [1, 10, 3, 18]. Le produit (calculé à l'aide d'ints 32 bits) est

(R + 2) * (R + 20) * (R + 6) * (R + 36) = 3376724311 Par conséquent, le hachage serait

3376724311/2 = 1688362155.

L'utilisation du double hachage (ou pour une surpuissance encore plus) en modifiant la valeur de R les identifierait avec succès comme des permutations avec une très forte probabilité.

Dans le doute
la source
1
Vous ne pouvez pas trier les chaînes car vous n'êtes pas autorisé à les modifier. Quant au hachage, c'est un algorithme aléatoire qui pourrait donner la mauvaise réponse.
Yuval Filmus
0

Disons que vous avez deux chaînes appelées s et t.

Vous pouvez utiliser des heuristiques pour vous assurer qu'elles ne sont pas inégales.

  1. s.length == t.length
  2. somme des caractères de s == somme des caractères en t
  3. [comme en 2. mais avec xor au lieu de sum]

Après cela, vous pouvez facilement exécuter un algorithme pour prouver que la chaîne est égale.

  1. trier une chaîne pour qu'elle soit égale à l'autre et comparer (O (n ^ 2))
  2. trier les deux et comparer (O (2n log (n))
  3. vérifier pour chaque caractère en s s'il y a les mêmes quantités dans les deux chaînes (O (n ^ 2))

Bien sûr, vous ne pouvez pas trier aussi rapidement si vous n'êtes pas autorisé à utiliser de l'espace supplémentaire. Donc, peu importe l'algorithme que vous choisissez - chaque algorithme devra fonctionner en temps O (n ^ 2) lorsqu'il n'y a que de l'espace O (1) et si l'heuristique n'a pas pu prouver qu'ils ne peuvent pas être égaux.

MurksVomOrk
la source
3
"La modification des cordes n'est en aucun cas autorisée. "
Bergi
0

En code de style C pour toute la routine:

for (int i = 0; i < n; i++) {
   int k = -1;
   next: for (int j = 0; j <= i; j++)
       if (A[j] == A[i]) {
          while (++k < n)
              if (B[k] == A[i])
                  continue next;
          return false; // note at this point j == i
       }
}
return true; 

Ou en pseudo code très verbeux (en utilisant une indexation basée sur 1)

// our loop invariant is that B contains a permutation of the letters
// in A[1]..A[i-1]
for i=1..n
   if !checkLetters(A, B, i)
      return false
return true

où la fonction checkLetters (A, B, i) vérifie que s'il y a M copies de A [i] dans A [1] .. A [i], alors il y a au moins M copies de A [i] dans B:

checkLetters(A,B,i)
    k = 0 // scan index into B
    for j=1..i
      if A[j] = A[i]
         k = findNextValue(B, k+1, A[i])
         if k > n
            return false
    return true

et la fonction findNextValue recherche dans B une valeur à partir d'un index et retourne l'index où il a été trouvé (ou n + 1 s'il n'est pas trouvé).

n2

MotiN
la source
Pouvez-vous s'il vous plaît convertir votre code C en pseudocode? Ce n'est pas un site de programmation.
Yuval Filmus
Cela semble être une autre variante de la réponse de Bergi (avec quelques différences sans conséquence).
Yuval Filmus
C'est similaire mais pas une variante. La réponse de Bergi estO(nm)où m = taille de l'alphabet. C'estO(n2).
MotiN
0

Je pense que c'est l'algorithme le plus simple (avec O(n3) temps, n longueur des cordes)

Parcourez string1et string2, pour chaque personnage, vérifiez la fréquence à laquelle il peut être trouvé dans string1et string2. Si un personnage est plus souvent dans une chaîne que dans l'autre, ce n'est pas une permutation. Si les fréquences de tous les caractères sont égales, les chaînes sont des permutations les unes des autres.

Voici un morceau de python pour rendre cela précis

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references string1 
    #  string2, it is not a copy
    for char in string:
      count1=0
      for char1 in string1:
        if  char==char1:
          count1+=1
      count2=0
      for char2 in string2:
        if  char==char2:
          count2+=1
      if count1!=count2:
        print('unbalanced character',char)
        return()
  print ("permutations")
  return()

check_if_permutations(s1,s2)

Le programme a besoin de quelques pointeurs vers des chaînes ( string, string1, string2, char, char1, char2) et les variables de tailleO(Journaln)pour compter ( count1, count2). Il doit vérifier si les caractères sont égaux ou non, mais il n'a besoin d'aucun ordre sur ces caractères. Peut-être qu'il a besoin de certaines variables pour les petits entiers (par exemple pour contenir des valeurs booléennes ou pour représenter la position de stringin [string1, string2].

Bien sûr, vous n'avez même pas besoin des variables de comptage, mais vous pouvez utiliser des pointeurs.

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references one of string1 
    # or string2, it is not a copy
    for char in string:
      # p1 and p2 should be views as pointers
      p1=0
      p2=0
      while (p1<len(string1)) and (p2<len(string2)):
        # p1>=len(string1): p1 points to beyond end of string
        while (p1<len(string1)) and (string1[p1]!=char) :
          p1+=1
        while(p2<len(string2)) and (string2[p2]!=char):
          p2+=1
        if (p1<len(string1)) != (p2<len(string2)):
          print('unbalanced character',char)
          return()
        p1+=1
        p2+=1
  print ("permutations")
  return()

check_if_permutations(s1,s2)

Ce deuxième programme a besoin de variables similaires à la première, sauf qu'il n'a pas besoin du O(Journal(n))-taille variables pour conserver les valeurs de comptage.

Donc, cela ne dépend pas n ou la taille de l'alphabet.

miracle173
la source
C'est la même chose que la solution de Bergi ci-dessous.
Yuval Filmus
@YuvalFilmus Non, il n'itère pas sur tout l'alphabet et son exécution ne dépend donc pas de la taille de l'alphabet. Il utilise uniquement les deux chaînes qui doivent être testées. De plus, le deuxième programme évite de compter.
miracle173
@YuvalFilmus Je vois maintenant que vos commentaires et d'autres pointent dans la direction de la façon dont j'ai utilisé dans mon programme.
miracle173