0Pricing
DSA Interview Prep · Leçon

Recherche avec caractères génériques et expressions régulières dans un trie

Prenez en charge la correspondance avec le caractère générique « . » en explorant tous les enfants à cette profondeur, afin de résoudre le problème de structure de données design-add-and-search-words.

Recherche avec caractères génériques et expressions régulières dans un trie est une leçon DSA Interview Prep gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage DSA Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours DSA Interview Prep comprend 4 leçons au total.

Le problème de la recherche avec caractère générique

La recherche standard dans un trie gère les caractères exacts. La recherche avec caractère générique ajoute un caractère spécial '.' qui correspond à n'importe quel caractère unique. Lorsque nous rencontrons un '.' pendant la recherche, au lieu de suivre un enfant précis, nous devons essayer tous les enfants — il s'agit d'une ramification. C'est l'idée centrale derrière LeetCode 211 « Concevoir une structure de données pour ajouter et rechercher des mots ». Chaque '.' multiplie le nombre de chemins de recherche par le nombre d'enfants à ce niveau.

Recherche récursive avec caractère générique

Implémentez la recherche avec caractère générique à l'aide d'un assistant DFS récursif. Pour chaque caractère du motif : s'il s'agit d'un caractère littéral, suivez l'enfant correspondant (ou renvoyez False s'il est absent) ; s'il s'agit de '.', appelez récursivement tous les enfants et renvoyez True si l'un d'eux réussit. À la fin du motif, renvoyez node.is_end.

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class WordDictionary:
    def __init__(self):
        self.root = TrieNode()
    
    def addWord(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def search(self, word):
        def dfs(node, i):
            if i == len(word):
                return node.is_end
            c = word[i]
            if c == '.':
                return any(dfs(child, i+1) for child in node.children.values())
            if c not in node.children:
                return False
            return dfs(node.children[c], i+1)
        return dfs(self.root, 0)

wd = WordDictionary()
wd.addWord('bad')
wd.addWord('dad')
wd.addWord('mad')
print(wd.search('.ad'))  # True
print(wd.search('b..'))  # True
print(wd.search('pad'))  # False

Pourquoi utiliser any() pour la ramification

Lorsqu'un '.' est rencontré, nous appelons any(dfs(child, i+1) for child in node.children.values()). Le générateur any() utilise une évaluation avec arrêt anticipé : il s'arrête dès qu'un enfant renvoie True. Cela évite les explorations inutiles. Dans le pire des cas (motif composé uniquement de '.'), nous explorons tous les chemins — la complexité est de O(26^k), où k est le nombre de points, ce qui rend des motifs comme '....' coûteux pour les tries volumineux.

Recherche itérative avec caractère générique et files

Une approche itérative utilise une file de paires (node, index). Commencez par (root, 0). Pour chaque paire, si index == len(word) et node.is_end, renvoyez True. Sinon, traitez le caractère courant : pour '.', ajoutez tous les enfants à la file ; pour un caractère littéral, ajoutez uniquement l'enfant correspondant. Il s'agit essentiellement d'un parcours BFS des chemins du trie.

from collections import deque

def search_iterative(root, word):
    queue = deque([(root, 0)])
    while queue:
        node, i = queue.popleft()
        if i == len(word):
            if node.is_end:
                return True
            continue
        c = word[i]
        if c == '.':
            for child in node.children.values():
                queue.append((child, i+1))
        elif c in node.children:
            queue.append((node.children[c], i+1))
    return False

print('Iterative BFS-based wildcard search')

Analyse de la complexité de la recherche avec caractère générique

Pour un motif sans caractères génériques, la recherche est en O(m). Pour un motif contenant k caractères génériques, le pire cas est O(26^k × m) — une complexité exponentielle par rapport au nombre de caractères génériques. En pratique, les caractères génériques sont généralement peu nombreux et le trie est peu profond, les performances sont donc acceptables. Pour les motifs entièrement composés de caractères génériques (par exemple, correspondant à tous les mots de longueur k), la recherche dégénère en un parcours complet du trie.

Recherche par expressions régulières au-delà des caractères génériques uniques

Passer aux expressions régulières complètes (par exemple, '*' correspondant à zéro caractère ou davantage) nécessite une gestion différente. Un '*' peut correspondre à n'importe quel suffixe ; lorsque nous le rencontrons, nous devons essayer tous les chemins du trie à partir du nœud courant. La véritable recherche par expressions régulières dans un trie est complexe — elle est généralement réservée aux constructions NFA/DFA. En entretien, les caractères génériques uniques ('.') constituent le motif standard.

Correspondance de motifs glob

La correspondance de motifs glob avec '?' (n'importe quel caractère unique) et '*' (n'importe quelle séquence, y compris une séquence vide) peut être implémentée avec DP. Si vous l'implémentez dans un trie, '?' correspond à une ramification sur un seul niveau (comme '.') et '*' correspond à un parcours DFS sur plusieurs niveaux. L'approche DP combinée est la suivante : dp[i][j] = True si pattern[0..i] correspond à string[0..j]. En entretien, l'intervieweur précise généralement quelle variante doit être implémentée.

Application pratique : routage d'adresses IP

Les tries avec caractères génériques sont utilisés dans les tables de routage IP, où '*' joue le rôle de caractère générique de préfixe. Un routeur stocke des préfixes de routes comme '192.168.*' et les compare aux adresses entrantes. La correspondance par préfixe le plus long (la route la plus spécifique l'emporte) est implémentée en parcourant le trie aussi profondément que possible et en utilisant la dernière correspondance rencontrée. Il s'agit d'une application concrète des opérations de préfixe et de caractère générique sur les tries.

Optimisation : élagage des branches mortes

Lorsqu'un nœud du trie n'a aucun enfant (c'est une feuille) et que is_end = False, toute recherche qui l'atteint renvoie False. Pendant la recherche avec caractère générique, ignorer ces nœuds terminaux sans issue avant l'appel récursif permet d'élaguer des appels inutiles. Le maintien d'un word_count dans chaque nœud (nombre total de mots dans le sous-arbre) permet d'ignorer tout un sous-arbre si aucun mot ne peut respecter les contraintes de longueur du motif restant.

Classe WordDictionary complète, prête pour un entretien

Une classe WordDictionary claire et prête pour un entretien, qui combine l'insertion et la recherche avec le caractère générique point dans une seule classe. Il s'agit de l'implémentation exacte attendue pour LeetCode 211. La recherche récursive avec any() à arrêt anticipé est concise et montre clairement la logique de ramification aux personnes qui mènent l'entretien.

class WordDictionary:
    def __init__(self):
        self.root = {}
    
    def addWord(self, word):
        node = self.root
        for c in word:
            node = node.setdefault(c, {})
        node['#'] = True
    
    def search(self, word):
        def dfs(node, i):
            if i == len(word):
                return '#' in node
            if word[i] == '.':
                return any(dfs(v, i+1) for k, v in node.items() if k != '#')
            nxt = node.get(word[i])
            return dfs(nxt, i+1) if nxt is not None else False
        return dfs(self.root, 0)

wd = WordDictionary()
for w in ['at','and','an','add']:
    wd.addWord(w)
print(wd.search('a.'))   # True (at, an)
print(wd.search('.nd'))  # True (and)
print(wd.search('...'))  # True (and, add)
print(wd.search('x.'))   # False

Utiliser setdefault pour un trie compact

dict.setdefault(key, default) renvoie la valeur associée à key si celle-ci est présente ; sinon, il insère default et le renvoie. L'utilisation de node.setdefault(c, {}) lors de l'insertion élimine la vérification if-else : elle crée le dictionnaire enfant s'il est absent et le renvoie dans tous les cas. L'insertion devient ainsi un parcours sur une seule ligne : for c in word: node = node.setdefault(c, {}). Une solution claire et idiomatique en Python.

Vérification rapide

Testez votre compréhension des concepts de structures de données et d'algorithmes — préparation aux entretiens de programmation présentés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que le caractère générique '.' nécessite une ramification vers tous les enfants à la position correspondante à l'aide d'un DFS récursif, que l'utilisation de any() avec un générateur fournit une évaluation avec arrêt anticipé pour mettre fin rapidement à la recherche, et que setdefault permet une insertion compacte du trie sur une seule ligne. Ensuite, nous combinerons trie et retour arrière pour résoudre Word Search II — trouver plusieurs mots simultanément sur une grille en deux dimensions.

Questions Fréquemment Posées

La leçon « Recherche avec caractères génériques et expressions régulières dans un trie » est-elle gratuite ?

Oui — le texte complet de « Recherche avec caractères génériques et expressions régulières dans un trie » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours DSA Interview Prep, passe à CoddyKit PRO. Le cours DSA Interview Prep comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Recherche avec caractères génériques et expressions régulières dans un trie » ?

Prenez en charge la correspondance avec le caractère générique « . » en explorant tous les enfants à cette profondeur, afin de résoudre le problème de structure de données design-add-and-search-words. Tu pratiques DSA Interview Prep avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer DSA Interview Prep ?

Aucune expérience préalable n'est requise. DSA Interview Prep sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Recherche avec caractères génériques et expressions régulières dans un trie » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon DSA Interview Prep ?

Oui. Chaque leçon DSA Interview Prep inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Classe TrieNode : insertion et recherche
  2. Recherche de préfixes et Starts-With
  3. Recherche avec caractères génériques et expressions régulières dans un trie
  4. Recherche de mots II : trie et retour arrière sur une grille
← Retour à DSA Interview Prep