Minimum Window Substring
On vous donne deux chaînes, s et t. Trouvez la plus courte sous-chaîne de s qui contient chaque caractère de t, en tenant compte des répétitions : si t contient une lettre deux fois, la sous-chaîne doit la contenir au moins deux fois. L’ordre n’a pas d’importance, et d’autres caractères peuvent également apparaître.
Si plusieurs sous-chaînes sont les plus courtes, renvoyez celle qui se trouve le plus à gauche. Si aucune ne contient tous les caractères de t, renvoyez une chaîne vide.
Fonction
- sstring
- la chaîne dans laquelle effectuer la recherche
- tstring
- les caractères que la fenêtre doit contenir, y compris les répétitions
- Renvoiestring
- la sous-chaîne la plus courte et, en cas d’égalité, la plus à gauche de s qui contient tous les caractères de t, ou une chaîne vide
Contraintes
1 ≤ s.length ≤ 5 × 1041 ≤ t.length ≤ 104settne contiennent que des lettres anglaises. Les lettres majuscules et minuscules sont des caractères différents.- Lorsque plusieurs sous-chaînes sont les plus courtes, la réponse est celle qui se trouve le plus à gauche ; lorsqu'il n'en existe aucune, c'est
"".
Exemples
- Entrée
- s = "mappingtheplan"t = "nap"
- Sortie
- "plan"
- Explication
- La première fenêtre en partant de la gauche qui contient un
n, unaet unpestappin, cinq caractères.plancontient les trois en quatre caractères, et aucune séquence de trois ne le fait.
- Entrée
- s = "banana"t = "aan"
- Sortie
- "ana"
- Explication
tdemande deux exemplaires deaet unn.anaà l’index 1 contient exactement cela. Un deuxièmeanacommence à l’index 3, et c’est celui de gauche qui l’emporte.
- Entrée
- s = "Coddy"t = "cd"
- Sortie
- ""
- Explication
- Le
CdansCoddyest une majuscule, un caractère différent dec. Aucune sous-chaîne ne contient decminuscule, donc la réponse est la chaîne vide.
Pour aller plus loin
Lorsque t comporte peu de lettres et que s est long, la majeure partie de s n’a jamais d’importance. Peux-tu faire en sorte que la fenêtre ne saute qu’entre les positions qui contiennent une lettre de t ?
Indices
Ouvrez-les un par un. Chacun en dévoile un peu plus.
Une fenêtre qui contient tout
tle contient toujours quand tu l’allonges, et une fenêtre qui omet quelque chose continue de l’omettre quand tu la raccourcis. Utilise cela pour éviter d’essayer chaque début avec chaque fin.Avancez un bord droit jusqu’à ce que la fenêtre couvre
t. Puis avancez le bord gauche tant que la fenêtre couvre encoret, en l’enregistrant à chaque fois. Aucun des deux bords n’a jamais besoin de reculer.Gardez un tableau indiquant combien d’exemplaires supplémentaires de chaque caractère la fenêtre nécessite, ainsi qu’un nombre,
missing, indiquant combien d’exemplaires lui manquent au total. Un caractère qui entre fait diminuermissinguniquement s’il était encore nécessaire, et un caractère qui sort le fait augmenter uniquement si la fenêtre n’en contient pas assez. La fenêtre couvretexactement lorsquemissingvaut 0.
Solution
Seuls les comptes des caractères dans une fenêtre importent, pas leur ordre. Déplace le bord droit jusqu’à ce que la fenêtre couvre t, puis le bord gauche tant qu’elle le couvre encore ; aucun des deux bords ne revient en arrière. Un compteur des caractères manquants te permet de savoir en une seule étape si la fenêtre couvre t.
Agrandissez une fenêtre à partir de chaque début
Correcte, mais ne termine pas sur les plus gros tests
Intuition
Fixez un début et agrandissez la fenêtre d’un caractère à la fois, en comptant chaque caractère. Après chaque étape, vérifiez chacune des u lettres utilisées dans t. La première extrémité qui convient donne la plus courte fenêtre couvrante à partir de ce début ; arrêtez-vous donc là. Les débuts sont parcourus de gauche à droite et seule une fenêtre strictement plus courte remplace la meilleure, les ex æquo conservent donc celle qui est la plus à gauche.
Si aucune fenêtre n’existe, chaque début parcourt s jusqu’à la fin : environ n²/2 étapes, soit 1.25 × 10^9 pour n = 5 × 10^4.
Algorithme
- Comptez le nombre d’occurrences de chaque lettre demandée par
t. - Pour chaque
start, réinitialisez une table de comptage et déplacezendvers la droite en ajoutants[end]. - Lorsque la fenêtre contient suffisamment d’occurrences de chaque lettre, conservez-la si elle est strictement plus courte que la meilleure fenêtre, puis passez au
startsuivant. - Retournez la meilleure fenêtre, ou
""si aucune ne contenaitt.
def minWindow(s, t):
need = [0] * 128 # copies of each character code that t asks for
for ch in t:
need[ord(ch)] += 1
letters = [c for c in range(128) if need[c] > 0]
best_start, best_len = 0, len(s) + 1
for start in range(len(s)):
have = [0] * 128 # counts inside s[start..end]
for end in range(start, len(s)):
have[ord(s[end])] += 1
if all(have[c] >= need[c] for c in letters):
# The first end that covers t gives the shortest window from this start.
if end - start + 1 < best_len:
best_start, best_len = start, end - start + 1
break
if best_len > len(s):
return ""
return s[best_start:best_start + best_len]Fenêtre glissante qui vérifie chaque lettre
Intuition
Ajouter des caractères à une fenêtre couvrante la maintient couvrante, et retirer des caractères d’une fenêtre à laquelle il manque quelque chose la laisse incomplète. Ainsi, lorsque le début se déplace vers la droite, la fin de la plus courte fenêtre couvrante ne se déplace jamais vers la gauche.
Aucune fenêtre n’est ignorée. Considère la meilleure fenêtre, de L à R. Pour que left dépasse L avant que right atteigne R, une fenêtre plus courte commençant à L devrait couvrir t. Ainsi, lorsque right atteint R, le rétrécissement fait avancer left jusqu’à L et enregistre cette fenêtre.
Chaque extrémité se déplace au plus n fois, mais chaque vérification lit jusqu’à u compteurs : O(n·u).
Algorithme
- Comptez les occurrences demandées par
t; définissezleft = 0et la meilleure longueur àn+1. - Pour chaque
right, ajoutezs[right]aux compteurs de la fenêtre. - Tant que chaque lettre de
test présente en quantité suffisante, enregistrez la fenêtre si elle est strictement plus courte, supprimezs[left]et avancezleft. - Retournez la meilleure fenêtre, ou
""si la meilleure longueur est toujoursn+1.
def minWindow(s, t):
need = [0] * 128 # copies of each character code that t asks for
for ch in t:
need[ord(ch)] += 1
letters = [c for c in range(128) if need[c] > 0]
have = [0] * 128 # counts inside s[left..right]
def covers():
for c in letters:
if have[c] < need[c]:
return False
return True
left = 0
best_start, best_len = 0, len(s) + 1
for right in range(len(s)):
have[ord(s[right])] += 1 # expand on the right
while covers(): # shrink from the left while the window still covers t
if right - left + 1 < best_len:
best_start, best_len = left, right - left + 1
have[ord(s[left])] -= 1
left += 1
if best_len > len(s):
return ""
return s[best_start:best_start + best_len]Fenêtre glissante avec un compteur de valeurs manquantes
Intuition
Remplace le balayage par un seul nombre. Soit need[c] le nombre d’occurrences de c demandées par t, moins le nombre d’occurrences dans la fenêtre ; une valeur négative indique des occurrences en surplus. Soit missing le nombre d’occurrences qui manquent encore à la fenêtre, initialisé à la longueur de t. La fenêtre couvre exactement t lorsque missing vaut 0.
Un caractère qui entre alors que son need est supérieur à 0 comble un manque, donc missing diminue. La suppression d’un caractère qui fait passer son need au-dessus de 0 crée un manque, donc missing augmente. Les occurrences en surplus n’ont aucun effet sur cette valeur. Pour banana avec t = aan, missing atteint d’abord 0 pour bana, et la suppression du b en surplus laisse ana, qui couvre toujours t.
Chaque caractère entre et sort une seule fois, pour un coût fixe : temps O(n + m), avec un tableau de 128 compteurs.
Algorithme
- Remplis
needavec les occurrences det; définismissingcomme la longueur det,left = 0et la meilleure longueur commen+1. - Pour chaque
right: sineed[s[right]]est supérieur à 0, diminuemissing; puis diminueneed[s[right]]. - Tant que
missingvaut 0, enregistre la fenêtre si elle est strictement plus courte. Augmenteneed[s[left]]; s’il est maintenant supérieur à 0, augmentemissing. Avanceleft. - Retourne la meilleure fenêtre, ou
""si la meilleure longueur vaut toujoursn+1.
def minWindow(s, t):
# need[c]: copies of c that t asks for minus copies inside the window.
# Positive means the window still lacks c; negative means it holds spares.
need = [0] * 128
for ch in t:
need[ord(ch)] += 1
missing = len(t) # characters of t the window does not cover yet
left = 0
best_start, best_len = 0, len(s) + 1
for right in range(len(s)):
c = ord(s[right])
if need[c] > 0: # this copy fills a gap
missing -= 1
need[c] -= 1
while missing == 0: # the window covers t: record it, then shrink
if right - left + 1 < best_len:
best_start, best_len = left, right - left + 1
c = ord(s[left])
need[c] += 1
if need[c] > 0: # gave away a copy t needs
missing += 1
left += 1
if best_len > len(s):
return ""
return s[best_start:best_start + best_len]
Pièges et cas limites
- Compter les lettres au lieu des occurrences.
t = aannécessite deuxa, doncbanne convient pas. - Modifier
missingpour chaque caractère qui entre ou sort. Unaen trop est alors compté, etmissingpeut atteindre 0 alors que la fenêtre ne contient pas len. - Remplacer la meilleure fenêtre lorsque les longueurs sont égales. Dans le deuxième exemple, cela renvoie le
anaà l’indice 3. - Utiliser
ncomme longueur signifiant « introuvable ». Lorsque la réponse correspond à touts, sa longueur est égalementn; commencez àn+1. - Un tableau de 26 cases indexé par
c - 'a'. Les lettres majuscules, comme dans le troisième exemple, n’y tiennent pas.
Questions fréquentes3
Quelle est la complexité temporelle de Minimum Window Substring ?
La fenêtre à compteur des caractères manquants s’exécute en O(n + m), où n et m sont les longueurs de s et de t. La construction de la table lit t une seule fois, et chaque caractère de s entre et sort de la fenêtre au plus une fois. La mémoire supplémentaire correspond à un compteur par code de caractère.
Pourquoi le bord gauche ne revient-il jamais en arrière ?
Le bord gauche ne franchit une position qu’une fois que la fenêtre qui commence à cet endroit a couvert t, et cette fenêtre était la plus courte fenêtre couvrante à partir de ce point de départ. Toute fenêtre partant de là et se terminant plus tard est plus longue, donc revenir en arrière ne peut jamais donner une meilleure réponse.
En quoi Minimum Window Substring diffère-t-il de la recherche d’un anagramme dans une chaîne ?
Un anagramme contient exactement les lettres de t ; sa fenêtre a donc une longueur fixe m et glisse d’un pas à la fois. Ici, les caractères supplémentaires sont autorisés, donc la longueur fait partie de la réponse : la fenêtre s’agrandit jusqu’à couvrir t, puis rétrécit tant qu’elle le couvre encore.
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.
def minWindow(s, t):
# Écrivez le code iciTest #1
Test #2
Test #3
Arguments
s = "mappingtheplan" t = "nap"
Résultat attendu
"plan"