Par exemple, supposons que la chaîne soit constituée des 10 premiers chiffres de pi, 3141592653
, et que la sous-séquence soit 123
. Notez que la séquence se produit deux fois:
3141592653
1 2 3
1 2 3
C'était une question d'entrevue à laquelle je ne pouvais pas répondre et je ne pouvais pas penser à un algorithme efficace et cela me dérange. Je pense qu'il devrait être possible de le faire avec une simple expression rationnelle, mais ceux comme 1.*2.*3
ne renvoient pas toutes les sous-séquences. Mon implémentation naïve en Python (compter les 3 pour chaque 2 après chaque 1) a fonctionné pendant une heure et ce n’est pas fait.
C'est un problème classique programmation dynamique (et qui n'est généralement pas résolu à l'aide d'expressions régulières).
Mon implémentation naïve (comptez les 3 pour chaque 2 après chaque 1) fonctionne depuis une heure et ce n'est pas terminé.
Ce serait une approche de recherche exhaustive qui s'exécute dans le temps exponentiel. (Je suis surpris qu'il dure des heures cependant).
Voici une suggestion pour une solution de programmation dynamique:
(Toutes mes excuses pour la description longue, mais chaque étape est vraiment simple alors supportez-moi ;-)
Si la sous-séquence est vide, une correspondance est trouvée (il ne reste plus de chiffres à faire correspondre!) Et nous retournons 1
Si la séquence d'entrée est vide, nous avons épuisé nos chiffres et nous ne pouvons trouver aucune correspondance, nous retournons donc 0.
(Ni la séquence ni la sous-séquence ne sont vides.)
(Supposons que " abcdef " indique la séquence d'entrée et " xyz "désigne la sous-séquence.)
Définissez result
sur 0
Ajoutez à la result
le nombre de correspondances pour bcdef et xyz (c.-à-d., ignore le premier chiffre entré et renvoie)
Si les deux premiers chiffres correspondent, c'est-à-dire, a = x
result
le nombre de correspondances pour bcdef et yz (c'est-à-dire, faire correspondre le premier chiffre de la sous-séquence et effectuer une récurrence sur les autres chiffres de la sous-séquence)Retourne result
Voici une illustration des appels récursifs pour l’entrée 1221/ 12 . (La sous-séquence est en gras, · représente une chaîne vide.)
Si implémenté naïvement, certains (sous-) problèmes sont résolus plusieurs fois (·/2 par exemple dans l'illustration ci-dessus). La programmation dynamique évite de tels calculs redondants en se souvenant des résultats des sous-problèmes précédemment résolus (généralement dans une table de recherche).
Dans ce cas particulier, nous avons créé une table avec
L'idée est que nous devions indiquer le nombre de correspondances pour 221/ 2 dans la ligne/colonne correspondante. Une fois cela fait, nous devrions avoir la solution finale dans la cellule 1221/ 12 .
Nous commençons à renseigner la table avec ce que nous savons immédiatement (les "cas de base"):
Quand il ne reste plus de chiffres de séquence, nous ne pouvons avoir aucune correspondance:
Nous procédons ensuite en renseignant le tableau de haut en bas/de gauche à droite selon la règle suivante:
Dans la cellule [ rangée ] [ col ] écrivez la valeur trouvée entre [ rangée -1] [col].
Intuitivement, cela signifie "Le nombre de correspondances pour 221/ 2 inclut toutes les correspondances pour 21/ 2 . "
Si séquence à la ligne , la ligne et le sous-programme à la colonne col commencent par le même chiffre, ajoutez la valeur trouvée à [ rangée - 1] [ col - 1] à la valeur qui vient d'être écrite dans [ rangée ] [ col ].
Intuitivement, cela signifie "Le nombre de correspondances pour 1221/ 12 inclut également toutes les correspondances pour 221/ 12 . "
Le résultat final se présente comme suit:
et la valeur dans la cellule en bas à droite est bien 2.
Pas en Python, (mes excuses).
class SubseqCounter {
String seq, subseq;
int[][] tbl;
public SubseqCounter(String seq, String subseq) {
this.seq = seq;
this.subseq = subseq;
}
public int countMatches() {
tbl = new int[seq.length() + 1][subseq.length() + 1];
for (int row = 0; row < tbl.length; row++)
for (int col = 0; col < tbl[row].length; col++)
tbl[row][col] = countMatchesFor(row, col);
return tbl[seq.length()][subseq.length()];
}
private int countMatchesFor(int seqDigitsLeft, int subseqDigitsLeft) {
if (subseqDigitsLeft == 0)
return 1;
if (seqDigitsLeft == 0)
return 0;
char currSeqDigit = seq.charAt(seq.length()-seqDigitsLeft);
char currSubseqDigit = subseq.charAt(subseq.length()-subseqDigitsLeft);
int result = 0;
if (currSeqDigit == currSubseqDigit)
result += tbl[seqDigitsLeft - 1][subseqDigitsLeft - 1];
result += tbl[seqDigitsLeft - 1][subseqDigitsLeft];
return result;
}
}
Un avantage supplémentaire de cette approche de type "à compléter" est qu'il est trivial de comprendre la complexité. Une quantité constante de travail est effectuée pour chaque cellule et nous avons des lignes de longueur de séquence et des colonnes de longueur de sous-séquence. La complexité est donc O (MN) où M et N désignent les longueurs des séquences.
Super réponse, aioobe ! Pour compléter votre réponse, quelques implémentations possibles en Python:
# straightforward, naïve solution; too slow!
def num_subsequences(seq, sub):
if not sub:
return 1
Elif not seq:
return 0
result = num_subsequences(seq[1:], sub)
if seq[0] == sub[0]:
result += num_subsequences(seq[1:], sub[1:])
return result
# top-down solution using explicit memoization
def num_subsequences(seq, sub):
m, n, cache = len(seq), len(sub), {}
def count(i, j):
if j == n:
return 1
Elif i == m:
return 0
k = (i, j)
if k not in cache:
cache[k] = count(i+1, j) + (count(i+1, j+1) if seq[i] == sub[j] else 0)
return cache[k]
return count(0, 0)
# top-down solution using the lru_cache decorator
# available from functools in python >= 3.2
from functools import lru_cache
def num_subsequences(seq, sub):
m, n = len(seq), len(sub)
@lru_cache(maxsize=None)
def count(i, j):
if j == n:
return 1
Elif i == m:
return 0
return count(i+1, j) + (count(i+1, j+1) if seq[i] == sub[j] else 0)
return count(0, 0)
# bottom-up, dynamic programming solution using a lookup table
def num_subsequences(seq, sub):
m, n = len(seq)+1, len(sub)+1
table = [[0]*n for i in xrange(m)]
def count(iseq, isub):
if not isub:
return 1
Elif not iseq:
return 0
return (table[iseq-1][isub] +
(table[iseq-1][isub-1] if seq[m-iseq-1] == sub[n-isub-1] else 0))
for row in xrange(m):
for col in xrange(n):
table[row][col] = count(row, col)
return table[m-1][n-1]
# bottom-up, dynamic programming solution using a single array
def num_subsequences(seq, sub):
m, n = len(seq), len(sub)
table = [0] * n
for i in xrange(m):
previous = 1
for j in xrange(n):
current = table[j]
if seq[i] == sub[j]:
table[j] += previous
previous = current
return table[n-1] if n else 1
Une façon de le faire serait d'utiliser deux listes. Appelez-les Ones
et OneTwos
.
Parcourez la chaîne, caractère par caractère.
1
, saisissez une entrée dans la liste Ones
.2
, parcourez la liste Ones
et ajoutez une entrée à la liste OneTwos
.3
, parcourez la liste OneTwos
et générez un 123
.Dans le cas général, cet algorithme sera très rapide puisqu'il s'agira d'un seul passage dans la chaîne et de plusieurs passages dans des listes normalement beaucoup plus petites. Les cas pathologiques vont le tuer, cependant. Imaginez une chaîne comme 111111222222333333
, mais avec chaque chiffre répété des centaines de fois.
from functools import lru_cache
def subseqsearch(string,substr):
substrset=set(substr)
#fixs has only element in substr
fixs = [i for i in string if i in substrset]
@lru_cache(maxsize=None) #memoisation decorator applyed to recs()
def recs(fi=0,si=0):
if si >= len(substr):
return 1
r=0
for i in range(fi,len(fixs)):
if substr[si] == fixs[i]:
r+=recs(i+1,si+1)
return r
return recs()
#test
from functools import reduce
def flat(i) : return reduce(lambda x,y:x+y,i,[])
N=5
string = flat([[i for j in range(10) ] for i in range(N)])
substr = flat([[i for j in range(5) ] for i in range(N)])
print("string:","".join(str(i) for i in string),"substr:","".join(str(i) for i in substr),sep="\n")
print("result:",subseqsearch(string,substr))
sortie (instantanément):
string:
00000000001111111111222222222233333333334444444444
substr:
0000011111222223333344444
result: 1016255020032
Une réponse Javascript basée sur la programmation dynamique de geeksforgeeks.org et la réponse de aioobe :
class SubseqCounter {
constructor(subseq, seq) {
this.seq = seq;
this.subseq = subseq;
this.tbl = Array(subseq.length + 1).fill().map(a => Array(seq.length + 1));
for (var i = 1; i <= subseq.length; i++)
this.tbl[i][0] = 0;
for (var j = 0; j <= seq.length; j++)
this.tbl[0][j] = 1;
}
countMatches() {
for (var row = 1; row < this.tbl.length; row++)
for (var col = 1; col < this.tbl[row].length; col++)
this.tbl[row][col] = this.countMatchesFor(row, col);
return this.tbl[this.subseq.length][this.seq.length];
}
countMatchesFor(subseqDigitsLeft, seqDigitsLeft) {
if (this.subseq.charAt(subseqDigitsLeft - 1) != this.seq.charAt(seqDigitsLeft - 1))
return this.tbl[subseqDigitsLeft][seqDigitsLeft - 1];
else
return this.tbl[subseqDigitsLeft][seqDigitsLeft - 1] + this.tbl[subseqDigitsLeft - 1][seqDigitsLeft - 1];
}
}
Ma tentative rapide:
def count_subseqs(string, subseq):
string = [c for c in string if c in subseq]
count = i = 0
for c in string:
if c == subseq[0]:
pos = 1
for c2 in string[i+1:]:
if c2 == subseq[pos]:
pos += 1
if pos == len(subseq):
count += 1
break
i += 1
return count
print count_subseqs(string='3141592653', subseq='123')
Edit: Celui-ci devrait être correct également si 1223 == 2
et les cas plus compliqués:
def count_subseqs(string, subseq):
string = [c for c in string if c in subseq]
i = 0
seqs = []
for c in string:
if c == subseq[0]:
pos = 1
seq = [1]
for c2 in string[i + 1:]:
if pos > len(subseq):
break
if pos < len(subseq) and c2 == subseq[pos]:
try:
seq[pos] += 1
except IndexError:
seq.append(1)
pos += 1
Elif pos > 1 and c2 == subseq[pos - 1]:
seq[pos - 1] += 1
if len(seq) == len(subseq):
seqs.append(seq)
i += 1
return sum(reduce(lambda x, y: x * y, seq) for seq in seqs)
assert count_subseqs(string='12', subseq='123') == 0
assert count_subseqs(string='1002', subseq='123') == 0
assert count_subseqs(string='0123', subseq='123') == 1
assert count_subseqs(string='0123', subseq='1230') == 0
assert count_subseqs(string='1223', subseq='123') == 2
assert count_subseqs(string='12223', subseq='123') == 3
assert count_subseqs(string='121323', subseq='123') == 3
assert count_subseqs(string='12233', subseq='123') == 4
assert count_subseqs(string='0123134', subseq='1234') == 2
assert count_subseqs(string='1221323', subseq='123') == 5
psh. Les solutions O(n) sont bien meilleures.
Pensez-y en construisant un arbre:
itérer le long de la chaîne si le caractère est '1', ajoutez un noeud à la racine de l'arbre . si le caractère est '2', ajoutez un enfant à chaque noeud de premier niveau . if le caractère est '3', ajoutez un enfant à chaque nœud de second niveau.
renvoie le nombre de nœuds de troisième couche.
ce serait inefficace, alors pourquoi ne pas stocker le nombre de nœuds à chaque profondeur:
infile >> in;
long results[3] = {0};
for(int i = 0; i < in.length(); ++i) {
switch(in[i]) {
case '1':
results[0]++;
break;
case '2':
results[1]+=results[0];
break;
case '3':
results[2]+=results[1];
break;
default:;
}
}
cout << results[2] << endl;
Remarquez, nous n'avons pas besoin de TROUVER toutes les séquences, nous avons seulement besoin de les COMPTER. Ainsi, tous les algorithmes qui recherchent des séquences sont excessivement complexes.
C'est tout. La complexité est O (N). Vraiment, pour une ligne de chiffres normale, le raccourcissement de la ligne source prend environ deux fois plus de temps.
Si la séquence est plus longue, de M membres par exemple, la procédure peut être répétée M fois. Et la complexité sera O (MN), où N sera déjà la longueur de la chaîne source raccourcie.