web-dev-qa-db-fra.com

Trouver toutes les sous-chaînes communes de deux chaînes données

Je suis tombé sur une déclaration de problème pour trouver le toutes les sous-chaînes communes entre les deux sous-chaînes données de telle sorte que dans tous les cas, vous devez imprimer la plus longue sous-chaîne. L'énoncé du problème est le suivant:

Écrivez un programme pour trouver les sous-chaînes communes entre les deux chaînes données. Cependant, n'incluez pas de sous-chaînes contenues dans des sous-chaînes communes plus longues.

Par exemple, étant donné les chaînes d'entrée eatsleepnightxyz et eatsleepabcxyz, les résultats devraient être:

  • eatsleep (en raison de eatsleepnightxyzeatsleepabcxyz)
  • xyz (en raison de eatsleepnightxyzeatsleepabcxyz)
  • a (en raison de eatsleepnightxyzeatsleepabcxyz)
  • t (en raison de eatsleepnightxyzeatsleepabcxyz)

Cependant, le jeu de résultats doit pas inclure e de eatsleepnightxyzeatsleepabcxyz, car les deux e sont déjà contenus dans le eatsleep mentionné ci-dessus. Vous ne devez pas non plus inclure ea, eat, ats, etc., car ceux-ci sont également tous couverts par eatsleep.

Dans ce cas, vous n'avez pas à utiliser les méthodes de l'utilitaire String comme: contains, indexOf, StringTokenizer, split et replace.

Mon algorithme est le suivant: je commence par la force brute et je passerai à une solution plus optimisée lorsque j'améliorerai ma compréhension de base.

 For String S1:
     Find all the substrings of S1 of all the lengths
     While doing so: Check if it is also a substring of 
     S2.

Essayez de comprendre la complexité temporelle de mon approche.

Soit les deux chaînes données n1-String et n2-String

  1. Le nombre de sous-chaînes de S1 est clairement n1 (n1 + 1)/2.
  2. Mais nous devons trouver la longueur moyenne d'une sous-chaîne de S1.
  3. Disons que c'est m. Nous trouverons m séparément.
  4. Complexité temporelle pour vérifier si une chaîne m est une sous-chaîne d'une chaîne n est O (n * m).
  5. Maintenant, nous vérifions que chaque chaîne m est une sous-chaîne de S2, qui est une chaîne n2.
  6. Ceci, comme nous l'avons vu ci-dessus, est un O (n2 m) algorithme.
  7. Le temps requis par l'algorithme global est alors
  8. Tn = (nombre de sous-chaînes dans S1) * (durée moyenne de sous-chaîne pour la procédure de comparaison de caractères)
  9. En effectuant certains calculs, je suis arrivé à la conclusion que la complexité temporelle est O (n3 m2)
  10. Maintenant, notre travail consiste à trouver m en termes de n1.

Essayez de trouver m en termes de n1.

Tn = (n) (1) + (n-1) (2) + (n-2) (3) + ..... + (2) (n-1) + (1) (n)
où Tn est la somme des longueurs de toutes les sous-chaînes.

La moyenne sera la division de cette somme par le nombre total de sous-chaînes produites.

Ceci est simplement un problème de sommation et de division dont la solution est la suivante O (n)

Donc...

Le temps d'exécution de mon algorithme est O (n ^ 5).

Dans cet esprit, j'ai écrit le code suivant:

 package pack.common.substrings;

 import Java.util.ArrayList;
 import Java.util.LinkedHashSet;
 import Java.util.List;
 import Java.util.Set;

 public class FindCommon2 {
    public static final Set<String> commonSubstrings = new      LinkedHashSet<String>();

 public static void main(String[] args) {
    printCommonSubstrings("neerajisgreat", "neerajisnotgreat");
    System.out.println(commonSubstrings);
}

 public static void printCommonSubstrings(String s1, String s2) {
    for (int i = 0; i < s1.length();) {
        List<String> list = new ArrayList<String>();
        for (int j = i; j < s1.length(); j++) {
            String subStr = s1.substring(i, j + 1);
            if (isSubstring(subStr, s2)) {
                list.add(subStr);
            }
        }
        if (!list.isEmpty()) {
            String s = list.get(list.size() - 1);
            commonSubstrings.add(s);
            i += s.length();
        }
    }
 }

 public static boolean isSubstring(String s1, String s2) {
    boolean isSubstring = true;
    int strLen = s2.length();
    int strToCheckLen = s1.length();
    if (strToCheckLen > strLen) {
        isSubstring = false;
    } else {
        for (int i = 0; i <= (strLen - strToCheckLen); i++) {
            int index = i;
            int startingIndex = i;
            for (int j = 0; j < strToCheckLen; j++) {
                if (!(s1.charAt(j) == s2.charAt(index))) {
                    break;
                } else {
                    index++;
                }
            }
            if ((index - startingIndex) < strToCheckLen) {
                isSubstring = false;
            } else {
                isSubstring = true;
                break;
            }
        }
    }
    return isSubstring;
 }
}

Explication pour mon code:

 printCommonSubstrings: Finds all the substrings of S1 and 
                        checks if it is also a substring of 
                        S2.
 isSubstring : As the name suggests, it checks if the given string 
               is a substring of the other string.

Problème: compte tenu des intrants

  S1 = “neerajisgreat”;
  S2 = “neerajisnotgreat”
  S3 = “rajeatneerajisnotgreat”

Dans le cas de S1 et S2, la sortie devrait être: neerajis et great mais dans le cas de S1 et S3, la sortie aurait dû être: neerajis, raj, great, eat mais je reçois toujours neerajis et great en sortie. Je dois comprendre cela.

Comment dois-je concevoir mon code?

23
neerajdorle

Vous feriez mieux de disposer d'un algorithme approprié pour la tâche plutôt que d'une approche par force brute. Wikipédia décrit deux solutions courantes au problème de sous-chaîne commun le plus long : arborescence de suffixes et programmation dynamique .

La solution de programmation dynamique prend O ( nm ) temps et O ( nm ) espace. C'est à peu près une simple Java du pseudocode Wikipedia pour la sous-chaîne commune la plus longue:

public static Set<String> longestCommonSubstrings(String s, String t) {
    int[][] table = new int[s.length()][t.length()];
    int longest = 0;
    Set<String> result = new HashSet<>();

    for (int i = 0; i < s.length(); i++) {
        for (int j = 0; j < t.length(); j++) {
            if (s.charAt(i) != t.charAt(j)) {
                continue;
            }

            table[i][j] = (i == 0 || j == 0) ? 1
                                             : 1 + table[i - 1][j - 1];
            if (table[i][j] > longest) {
                longest = table[i][j];
                result.clear();
            }
            if (table[i][j] == longest) {
                result.add(s.substring(i - longest + 1, i + 1));
            }
        }
    }
    return result;
}

Maintenant, vous voulez toutes les sous-chaînes courantes, pas seulement les plus longues. Vous pouvez améliorer cet algorithme pour inclure des résultats plus courts. Examinons le tableau pour les exemples d'entrées eatsleepnightxyz et eatsleepabcxyz:

  e a t s l e e p a b c x y z
e 1 0 0 0 0 1 1 0 0 0 0 0 0 0
a 0 2 0 0 0 0 0 0 1 0 0 0 0 0
t 0 0 3 0 0 0 0 0 0 0 0 0 0 0
s 0 0 0 4 0 0 0 0 0 0 0 0 0 0
l 0 0 0 0 5 0 0 0 0 0 0 0 0 0
e 1 0 0 0 0 6 1 0 0 0 0 0 0 0
e 1 0 0 0 0 1 7 0 0 0 0 0 0 0
p 0 0 0 0 0 0 0 8 0 0 0 0 0 0
n 0 0 0 0 0 0 0 0 0 0 0 0 0 0
i 0 0 0 0 0 0 0 0 0 0 0 0 0 0
g 0 0 0 0 0 0 0 0 0 0 0 0 0 0
h 0 0 0 0 0 0 0 0 0 0 0 0 0 0
t 0 0 1 0 0 0 0 0 0 0 0 0 0 0
x 0 0 0 0 0 0 0 0 0 0 0 1 0 0
y 0 0 0 0 0 0 0 0 0 0 0 0 2 0
z 0 0 0 0 0 0 0 0 0 0 0 0 0 3
  • Le résultat eatsleep est évident: c'est le 12345678 séquence diagonale en haut à gauche.
  • Le résultat xyz est le 123 diagonale en bas à droite.
  • Le résultat a est indiqué par le 1 en haut (deuxième ligne, neuvième colonne).
  • Le résultat t est indiqué par le 1 en bas à gauche.

Et les autres 1s à gauche, en haut et à côté de 6 et 7? Ceux-ci ne comptent pas car ils apparaissent dans le rectangle formé par le 12345678 diagonal - en d'autres termes, ils sont déjà couverts par eatsleep.

Je recommande de faire un passage en ne faisant que construire la table. Ensuite, effectuez un deuxième passage, en itérant vers l'arrière à partir du bas à droite, pour rassembler l'ensemble de résultats.

17
200_success

Typiquement, ce type de correspondance de sous-chaînes est effectué à l'aide d'une structure de données distincte appelée Trie (essai prononcé). La variante spécifique qui convient le mieux à ce problème est un arborescence de suffixes . Votre première étape devrait être de prendre vos entrées et de construire un arbre de suffixes. Ensuite, vous devrez utiliser l'arborescence des suffixes pour déterminer la sous-chaîne commune la plus longue, ce qui est un bon exercice.

5
ktbiz