Voici un autre problème qui demande comment trouver le nombre de sous-séquences distinctes d'une chaîne?
Par exemple,
Contribution
AAA
ABCDEFG
CODECRAFTSortie
4
128
496
Comment puis-je résoudre ce problème ?
C'est un problème de programmation dynamique classique.
Laisser:
dp[i] = number of distinct subsequences ending with a[i]
sum[i] = dp[1] + dp[2] + ... + dp[i]. So sum[n] will be your answer.
last[i] = last position of character i in the given string.
Une chaîne nulle a une sous-séquence, donc dp[0] = 1
.
read a
n = strlen(a)
for i = 1 to n
dp[i] = sum[i - 1] - sum[last[a[i]] - 1]
sum[i] = sum[i - 1] + dp[i]
last[a[i]] = i
return sum[n]
Explication
dp[i] = sum[i - 1] - sum[last[a[i]] - 1]
Initialement, nous supposons que nous pouvons ajouter a[i]
à toutes les sous-séquences se terminant par les caractères précédents, mais cela pourrait violer la condition selon laquelle les sous-séquences comptées doivent être distinctes. Rappelez-vous que last[a[i]]
nous donne la dernière position sur laquelle a[i]
est apparu jusqu'à maintenant. Les seules sous-séquences que nous surestimons sont celles auxquelles le précédent a[i]
a été ajouté. Nous soustrayons donc ces dernières.
sum[i] = sum[i - 1] + dp[i]
last[a[i]] = i
Mettez à jour ces valeurs selon leur définition.
Si votre indexation commence à 0, utilisez a[i - 1]
partout où j'ai utilisé a[i]
. Pensez également à envelopper vos calculs dans une fonction mod
si vous souhaitez soumettre du code. Ceci devrait être implémenté comme ceci:
mod(x) = (x % m + m) % m
Afin de gérer correctement les valeurs négatives dans certains langages (tels que C/C++).
Il existe une solution plus facile à ce problème.
L'idée est la suivante: si tous les caractères de la chaîne sont distincts, le nombre total de sous-séquences est égal à 2^n.
. Maintenant, si nous trouvons un caractère déjà présent, nous devrions considérer sa dernière occurrence uniquement (sinon la séquence ne sera pas distincte). Nous devons donc soustraire le nombre de sous-séquences en raison de son occurrence précédente.
Ma mise en oeuvre est la suivante:
read s
dp[0] = 1
len = strlen(s)
last[s.length()] = {-1} //declaring `last` array with same as length of string `s` and all elements initialized with -1.
for (i = 1; i <= len; i++)
{
dp[i] = (dp[i - 1] * 2)
if (last[s[i]] > 0) dp[i] = (dp[i] - dp[last[s[i]] - 1])
last[s[i]] = i
}
Voici monCODE:
#include<iostream>
typedef long long ll;
ll fun(std::string s,ll visited[256],ll n,ll L[]){
ll ans=0;
if(n<0){
return 1;
}
//std::cout<<s.substr(0,n+1)<<" "<<n<<endl;
ans=fun(s,visited,n-1,L);
L[n]=ans;
ans=ans*2;
if(visited[int(s[n])]>=0){
ans -= L[visited[int(s[n])]];
}
visited[int(s[n])]=n;
return ans;
}
int main(){
std::string s;
std::cin>>s;
ll n=s.length();
ll visited[256];
ll L[n];
memset(visited,-1,sizeof(visited));
memset(L,-1,sizeof(L));
std::cout<<fun(s,visited,n-1,L);
return 0;
}
Explication :
Je numérise à partir du dos d'une chaîne, c'est-à-dire du dernier élément au premier et envoie donc les premiers caractères n-1
pour une analyse plus poussée dans la récursivité.
Une fois n==-1 or n<0(both are same)
, j'atteins la chaîne vide et renvoie 1 car non. des sous-séquences d'une chaîne vide vaut 1.
Ainsi, en revenant de la récursion, nous savons que l'ajout du caractère non dupliqué actuel à la chaîne précédente double le n °. de sous-séquences. Le doublage se produit car je peux maintenant ajouter ce caractère à la fin de toutes les sous-séquences précédentes. Donc, with
et without
, ce caractère représente le double de toutes les sous-séquences précédentes.
En supposant que le caractère actuel ne soit pas un doublon, je multiplie le no précédent. de sous-séquences avec 2.
Après le non total des sous-séquences des premiers caractères n-1
ont été calculées, nous les doublons pour les premiers caractères n
.
Mais supposons que le caractère actuellement rencontré (nième caractère) ait déjà été présent dans les premiers n-1
caractères précédents (c'est-à-dire trouvé dans la chaîne s [0 ... n-1] (Remarque: s [n] est le caractère actuel). )), alors nous devons soustraire ces non. de sous-séquences possibles allant de (excluant) cette partie de s lors de la dernière fois où ce caractère courant a été rencontré et qui a déjà été calculée et stockée dans L ['ce caractère particulier'].
c'est-à-dire - BACA
- pour la chaîne donnée, le 4ème A
a déjà été rencontré auparavant (en revenant de la récursion, nous rencontrons d'abord B
, puis A
, puis C
et enfin A
) et nous déduisons donc le non. des sous-séquences calculées jusqu'à (excluant) la 2e A
(qui est 2 (car le nombre de sous-séquences précédant A
est égal à 2)).
Donc, chaque fois que nous avons calculé le non. des sous-séquences pour les premiers n-1
caractères, nous les stockons dans le tableau L.
Remarque : L [k] enregistre le no. des sous-séquences avant le kème indice.
J'ai utilisé le tableau visité pour vérifier si le personnage auquel je suis présent a déjà été scanné ou non.
Lorsque je rencontre le caractère actuel, je mets à jour le tableau visité avec la position de la position actuelle sous la forme n
. Cela doit être fait car nous devons exclure les séquences en double.
Remarque : visited[]
est initialisé avec tout -1 car la position de tout caractère de la chaîne s
est non négative (indexation basée sur 0).
Résumé :
How do you arrive at the number of duplicates? Let's say the last occurrence of current character at i, was at j'th position. Then, we will have duplicate subsequences: consider starting with i'th character and then all subsequences possible from [0,j-1] vs. starting at j'th character and then all subsequences possible from [0,j-1]. So, to eliminate this, you subtract the number of subsequences possible from upto (excluding) j with L[0]=1 mean that upto(excluding 0), no. of subseq are 1(empty string has 1 subsequence).