0Pricing
DSA Interview Prep · Aula

Busca com curingas e expressões regulares em uma trie

Adicione suporte à correspondência do curinga '.' explorando todos os filhos nessa profundidade e resolva o problema de projetar uma estrutura de dados para adicionar e buscar palavras.

Busca com curingas e expressões regulares em uma trie é uma aula grátis de DSA Interview Prep no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de DSA Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DSA Interview Prep inclui 4 aulas no total.

O problema da busca com curingas

A busca padrão em uma árvore de prefixos lida com caracteres exatos. A busca com curingas adiciona um caractere especial '.' que corresponde a qualquer caractere individual. Ao encontrar um '.' durante a busca, em vez de seguir um único filho específico, precisamos tentar todos os filhos — uma ramificação múltipla. Essa é a ideia central por trás do LeetCode 211, "Projetar uma Estrutura de Dados para Adicionar e Buscar Palavras". Cada '.' multiplica os caminhos de busca pelo número de filhos naquele nível.

Busca recursiva com curingas

Implemente a busca com curingas usando um auxiliar recursivo de DFS. Para cada caractere do padrão: se for um caractere literal, siga o filho específico (ou retorne False se ele não existir); se for '.', faça a recursão em todos os filhos e retorne True se algum deles tiver sucesso. No final do padrão, retorne 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

Por que usar any() para a ramificação múltipla

Quando um '.' é encontrado, chamamos any(dfs(child, i+1) for child in node.children.values()). O gerador any() usa avaliação de curto-circuito — ele para assim que um filho retorna True. Isso evita explorações desnecessárias. No pior caso (um padrão composto apenas por '.'), exploramos todos os caminhos — a complexidade é O(26^k), onde k é o número de pontos, tornando padrões como '....' dispendiosos em árvores de prefixos grandes.

Busca iterativa com curingas usando filas

Uma abordagem iterativa usa uma fila de pares (node, index). Comece com (root, 0). Para cada par, se index == len(word) e node.is_end, retorne True. Caso contrário, processe o caractere atual: para '.', enfileire todos os filhos; para um literal, enfileire apenas o filho correspondente. Isso é essencialmente um BFS sobre os caminhos da árvore de prefixos.

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')

Análise de complexidade da busca com curingas

Para um padrão sem curingas, a busca é O(m). Para um padrão com k curingas, o pior caso é O(26^k × m) — exponencial em relação ao número de curingas. Na prática, os curingas geralmente são esparsos e a árvore de prefixos é rasa, portanto o desempenho é aceitável. Para padrões compostos inteiramente por curingas (por exemplo, que correspondem a todas as palavras de comprimento k), o processo se reduz ao percurso completo da árvore de prefixos.

Busca com expressões regulares além de curingas de um caractere

Estender a busca para expressões regulares completas (por exemplo, '*' correspondendo a zero ou mais caracteres) exige um tratamento diferente. Um '*' pode corresponder a qualquer sufixo; portanto, ao encontrá-lo, precisamos tentar todos os caminhos da árvore de prefixos a partir do nó atual. A correspondência real de expressões regulares em uma árvore de prefixos é complexa — geralmente fica reservada para construções de NFA/DFA. Em entrevistas, os curingas de um único caractere ('.') são o padrão.

Correspondência de padrões glob

A correspondência de padrões glob com '?' (qualquer caractere individual) e '*' (qualquer sequência, inclusive vazia) pode ser implementada com DP. Se for implementada em uma árvore de prefixos, '?' corresponde a uma ramificação múltipla de um único nível (como '.') e '*' corresponde a um DFS de vários níveis. A abordagem combinada com DP: dp[i][j] = True se pattern[0..i] corresponder a string[0..j]. Normalmente, o entrevistador especifica qual variante deve ser implementada.

Aplicação prática: roteamento de endereços IP

Árvores de prefixos com curingas são usadas em tabelas de roteamento de IP, nas quais '*' atua como um curinga de prefixo. Um roteador armazena prefixos de rotas como '192.168.*' e os compara com os endereços recebidos. A correspondência do prefixo mais longo (a rota mais específica vence) é implementada percorrendo a árvore de prefixos o mais profundamente possível e usando a última correspondência encontrada. Esta é uma aplicação real das operações de prefixo e de curinga em árvores de prefixos.

Otimização: podando ramos mortos

Quando um nó da árvore de prefixos não tem filhos (é uma folha) e is_end = False, qualquer busca que o alcance retorna False. Durante a busca com curingas, ignorar esses nós sem saída antes de fazer a recursão pode podar chamadas desnecessárias. Manter um word_count em cada nó (o total de palavras na subárvore) permite ignorar uma subárvore inteira se nenhuma palavra puder corresponder às restrições de comprimento restantes do padrão.

Classe WordDictionary completa (pronta para entrevistas)

Uma classe WordDictionary limpa e pronta para entrevistas, que combina inserção e busca com curinga de ponto em uma única classe. Esta é a implementação exata esperada para o LeetCode 211. A busca recursiva com any() de curto-circuito é concisa e demonstra claramente a lógica de ramificação múltipla para os entrevistadores.

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

Usando setdefault para uma árvore de prefixos compacta

dict.setdefault(key, default) retorna o valor de key se ela estiver presente; caso contrário, insere default e o retorna. Usar node.setdefault(c, {}) na inserção elimina a verificação if-else: cria o dicionário filho se ele não existir e o retorna de qualquer forma. Isso transforma a inserção em um percurso de uma única linha: for c in word: node = node.setdefault(c, {}). Limpo e idiomático em Python.

Verificação rápida

Teste sua compreensão dos conceitos de Estruturas de Dados e Algoritmos — Preparação para Entrevistas de Programação apresentados nesta lição.

Recapitulação da lição

Nesta lição, você aprendeu: o curinga '.' exige uma ramificação múltipla para todos os filhos na posição correspondente, usando DFS recursivo, usar any() com um gerador fornece avaliação de curto-circuito para uma interrupção antecipada e setdefault permite uma inserção compacta da árvore de prefixos em uma única linha. A seguir, combinaremos árvore de prefixos e retrocesso para resolver a Busca de Palavras II — encontrando várias palavras simultaneamente em uma matriz bidimensional.

Perguntas Frequentes

A aula “Busca com curingas e expressões regulares em uma trie” é grátis?

Sim — o texto completo de “Busca com curingas e expressões regulares em uma trie” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de DSA Interview Prep, atualize para CoddyKit PRO. O curso de DSA Interview Prep inclui 4 aulas no total.

O que vou aprender em “Busca com curingas e expressões regulares em uma trie”?

Adicione suporte à correspondência do curinga '.' explorando todos os filhos nessa profundidade e resolva o problema de projetar uma estrutura de dados para adicionar e buscar palavras. Você pratica DSA Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar DSA Interview Prep?

Nenhuma experiência prévia é necessária. DSA Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Busca com curingas e expressões regulares em uma trie”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de DSA Interview Prep?

Sim. Cada aula de DSA Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Classe TrieNode: inserção e busca
  2. Busca por prefixo e começa com
  3. Busca com curingas e expressões regulares em uma trie
  4. Busca de palavras II: trie + retrocesso em uma grade
← Voltar para DSA Interview Prep