Is Subsequence
On vous donne deux chaînes, s et t. Renvoyez true si vous pouvez transformer t en s en supprimant certaines de ses lettres (éventuellement aucune), tout en conservant l’ordre des lettres restantes, et false sinon. Par exemple, ace est une sous-séquence de abcde, mais aec n’en est pas une.
Fonction
- sstring
- la chaîne à rechercher
- tstring
- la chaîne dont supprimer des lettres
- Renvoieboolean
- vrai si s peut être lu dans t dans l’ordre, éventuellement avec des écarts
Contraintes
1 ≤ s.length ≤ 3 × 1041 ≤ t.length ≤ 5 × 104settne contiennent que des lettres minuscules de l’alphabet anglais.
Exemples
- Entrée
- s = "ace"t = "abcde"
- Sortie
- true
- Explication
- Supprimez
betddeabcdeet il resteace, dans le même ordre.
- Entrée
- s = "aec"t = "abcde"
- Sortie
- false
- Explication
tcontient les trois lettres, mais le seulcse trouve avant le seule. Après avoir utilisé leeà l’index 4, il ne reste aucuncà sa droite.
- Entrée
- s = "moon"t = "monsoon"
- Sortie
- true
- Explication
- Utilisez le
mà l’index 0, lesoaux index 1 et 4, et lenà l’index 6 demonsoon. Les lettres entre les deux sont supprimées.
+20 tests cachés à la soumission
Pour aller plus loin
Supposons que t reste inchangé et que tu doives vérifier un million de chaînes différentes s par rapport à lui. Comment préparerais-tu t pour que chaque vérification soit plus rapide que de relire tout t ?
Indices
Ouvrez-les un par un. Chacun en dévoile un peu plus.
Regarde la première lettre de
s. Quelle occurrence de celle-ci danstdois-tu utiliser ?Utilisez la première copie. En prendre une plus tardive ne peut laisser que moins de
tpour le reste des, donc le choix le plus précoce n’est jamais moins bon.Gardez un indice dans
set un danst. Parcoureztune lettre à la fois, avancez l’indice danssà chaque correspondance, et vérifiez à la fin s’il a atteint la fin des.
Solution
Une sous-séquence peut ignorer des lettres de t n’importe où, ce qui peut donner l’impression qu’il faut essayer de nombreuses façons de placer s dans t. Ce n’est pas nécessaire. Faire correspondre chaque lettre de s au premier endroit où elle peut aller n’est jamais moins avantageux qu’un autre choix, et transforme la recherche en un seul parcours de gauche à droite avec deux pointeurs.
Programmation dynamique sur les préfixes
Correcte, mais ne termine pas sur les plus gros tests
Intuition
Pose une question plus petite : les i premières lettres de s peuvent-elles tenir dans les j premières lettres de t ? Appelons la réponse dp[i][j]. Si elles tiennent dans t[:j-1], elles tiennent aussi dans t[:j], puisque tu peux supprimer t[j-1]. Si s[i-1] est égal à t[j-1], tu peux aussi utiliser cette lettre, et les i-1 premières lettres de s doivent alors tenir dans t[:j-1]. Donc dp[i][j] = dp[i][j-1] or (s[i-1] == t[j-1] and dp[i-1][j-1]), et le préfixe vide de s tient partout.
La ligne i ne lit que la ligne i-1, donc deux lignes de longueur m+1 suffisent. La réponse se trouve dans la dernière cellule de la dernière ligne.
C’est la même table que celle que tu construis pour la plus longue sous-séquence commune, et elle est correcte, mais elle remplit chaque cellule. Avec s de 25 000 lettres et t de 50 000, cela représente 1.25 × 10^9 cellules, bien plus que ce qu’exige un seul passage sur les deux chaînes.
Algorithme
- Créez une ligne
prevdem+1valeurs, toutes égales àtrue: unsvide correspond à chaque préfixe det. - Pour chaque
ide 1 àn, créez une lignecuraveccur[0] = false. - Pour chaque
jde 1 àm, définissezcur[j]àcur[j-1], ou àprev[j-1]lorsques[i-1]est égal àt[j-1]. - Remplacez
prevparcur. - Retournez
prev[m].
def isSubsequence(s, t):
n, m = len(s), len(t)
# prev[j]: the first i-1 letters of s fit inside t[:j]. An empty s fits anywhere.
prev = [True] * (m + 1)
for i in range(1, n + 1):
cur = [False] * (m + 1)
for j in range(1, m + 1):
cur[j] = cur[j - 1] or (s[i - 1] == t[j - 1] and prev[j - 1])
prev = cur
return prev[m]Deux pointeurs avec appariement glouton
Intuition
Parcourez t de gauche à droite et gardez un pointeur i vers la prochaine lettre de s qu’il vous reste à trouver. Lorsque t[j] est égal à s[i], utilisez-le et avancez i. Dans tous les cas, avancez j. Si i atteint la fin de s, chaque lettre a trouvé sa place dans l’ordre.
Pourquoi peut-on prendre sans risque la première correspondance ? Supposons qu’un placement valide utilise une occurrence ultérieure de s[i]. En la remplaçant par la première occurrence, on conserve l’ordre et il reste davantage de lettres de t à droite pour le reste de s ; le choix glouton ne fait donc jamais perdre un placement existant. Pour moon dans monsoon, le pointeur prend le o à l’index 1, ignore n et s, prend le o à l’index 4 et s’arrête sur le n à l’index 6.
j parcourt chaque lettre de t une seule fois et i avance uniquement, donc la boucle s’exécute au plus m fois. Deux index suffisent comme mémoire.
Algorithme
- Définis
i = 0poursetj = 0pourt. - Tant que les deux index sont dans leurs chaînes, compare
s[i]àt[j]. - S’ils sont égaux, incrémente
i. - Incrémente
jdans tous les cas. - Renvoie si
iest égal à la longueur des.
def isSubsequence(s, t):
i = j = 0
while i < len(s) and j < len(t):
if s[i] == t[j]:
i += 1
j += 1
return i == len(s)
Pièges et cas limites
La boucle à deux pointeurs est courte, et ses bogues se nichent dans les cas limites.
- Chercher chaque lettre de
sn’importe où danstau lieu de la chercher après la correspondance précédente. Cela accepteaecdansabcde, alors que l’ordre n’est pas respecté. - Utiliser deux fois la même occurrence d’une lettre.
noonn’est pas une sous-séquence demoon:moonne contient qu’un seuln, à l’index 3, et il ne peut pas être à la fois la première et la dernière lettre denoon. - Renvoyer si
ja atteint la fin det. La boucle se termine souvent à cet endroit, quesait été trouvé ou non ; seulivous le dit. - Oublier que
speut être plus long quet. Pourabccomparé àab, il faut renvoyerfalse, ce que fait la boucle à condition qu’elle s’arrête quandtest épuisé. - Lire
s[i]après queia atteint la fin des. En Python ou en Java, cette lecture déclenche une exception : vérifiez donciavant de comparer.
Questions fréquentes4
Quelle est la complexité temporelle de Is Subsequence ?
La solution à deux pointeurs s’exécute en O(n + m) temps, où n et m sont les longueurs de s et t, et utilise O(1) mémoire supplémentaire. En pratique, la boucle s’arrête après au plus m étapes. Le tableau des préfixes prend O(n × m) temps.
Pourquoi l’approche gloutonne à deux pointeurs fonctionne-t-elle pour Is Subsequence ?
Faire correspondre une lettre de s à sa position la plus précoce possible dans t laisse la plus longue partie possible de t pour les lettres restantes. Toute position qui utilise une occurrence ultérieure peut être modifiée pour utiliser l’occurrence précédente sans rompre l’ordre ; ainsi, s’il existe une position possible, la méthode gloutonne la trouve.
Comment vérifier rapidement plusieurs chaînes avec le même t ?
Prépare t une seule fois : pour chaque lettre, stocke la liste triée des index où elle apparaît. Pour placer s[i], effectue une recherche binaire dans la liste de cette lettre afin de trouver le premier index après la correspondance précédente. Chaque vérification coûte alors O(n log m) au lieu de O(m).
Quelle est la différence entre une sous-séquence et une sous-chaîne ?
Une sous-chaîne est un bloc de lettres consécutives, tandis qu’une sous-séquence peut sauter des lettres tant que l’ordre reste le même. ace est une sous-séquence de abcde, mais pas une sous-chaîne de celui-ci. Toute sous-chaîne est une sous-séquence, mais l’inverse n’est pas vrai.
Problèmes similaires
Des problèmes qui reposent sur les mêmes idées. En résoudre deux ou trois, c’est ce qui ancre un schéma.
Python
def isSubsequence(s, t):
# Écrivez le code iciCas 1
Cas 2
Cas 3
Entrée
s = "ace" t = "abcde"
Attendu
true