0Pricing
DSA Interview Prep · Lezione

Ricerca per prefisso e Starts-With

Aggiunga un metodo starts_with che restituisca true se una parola inserita condivide un determinato prefisso e lo usi per implementare i suggerimenti di completamento automatico.

Ricerca per prefisso e Starts-With è una lezione DSA Interview Prep gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA Interview Prep include 4 lezioni in totale.

La potenza delle query sui prefissi

Il vantaggio distintivo del Trie rispetto a una mappa hash è l'efficienza nelle query sui prefissi. Una query su un prefisso risponde a domande come: «quante parole memorizzate iniziano con questo prefisso?», «quali sono tutte le parole memorizzate con questo prefisso?» o, semplicemente, «esiste una parola con questo prefisso?». Queste query hanno complessità O(p), dove p è la lunghezza del prefisso, indipendentemente dal numero totale di parole memorizzate: per questo i Trie sono ideali per il completamento automatico e i suggerimenti di ricerca.

Il metodo starts_with

starts_with(prefix) restituisce True se una qualsiasi parola memorizzata inizia con il prefisso indicato. Si percorra il Trie seguendo ogni carattere del prefisso. Se è possibile seguire tutti i caratteri senza incontrare un arco mancante, il prefisso esiste e almeno una parola lo utilizza. L'implementazione è identica a quella di search, con l'unica differenza che si restituisce True non appena termina il percorso: non si verifica is_end.

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

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(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 starts_with(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node.children:
                return False
            node = node.children[c]
        return True

t = Trie()
for w in ['hello','help','world','word']:
    t.insert(w)
print(t.starts_with('hel'))   # True
print(t.starts_with('wor'))   # True
print(t.starts_with('xyz'))   # False

Completamento automatico: trovare tutte le parole con un prefisso

Per implementare il completamento automatico, si percorra il Trie fino al nodo finale del prefisso, quindi si esegua una DFS, o una BFS, a partire da quel nodo per raccogliere tutte le parole che si diramano da esso. Si anteponga il prefisso a ogni suffisso raccolto per ricostruire le parole complete. Questa operazione ha complessità O(p + W), dove W è il numero totale di caratteri contenuti in tutte le parole corrispondenti.

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

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(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 autocomplete(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node.children:
                return []
            node = node.children[c]
        # DFS from prefix end node
        results = []
        def dfs(n, path):
            if n.is_end:
                results.append(prefix + path)
            for char, child in n.children.items():
                dfs(child, path + char)
        dfs(node, '')
        return results

t = Trie()
for w in ['apple','app','application','apply','apt']:
    t.insert(w)
print(t.autocomplete('app'))  # ['app','apple','apply','application']

Restituire suggerimenti ordinati

Per ottenere un completamento automatico ordinato, si percorrano i figli in ordine alfabetico durante la DFS, iterando su sorted(node.children.items()). Poiché i figli sono memorizzati in un dict, questo aggiunge un overhead di O(ALPHABET_SIZE × depth), ma garantisce risultati ordinati lessicograficamente. Un Trie basato su array percorre sempre i figli in ordine alfabetico, poiché gli indici da 0 a 25 sono ordinati.

def dfs_sorted(node, prefix, results):
    if node.is_end:
        results.append(prefix)
    for char in sorted(node.children.keys()):  # alphabetical order
        dfs_sorted(node.children[char], prefix + char, results)

print('Iterating children in sorted order gives lex-sorted suggestions')

I primi k suggerimenti di completamento automatico

Per ottenere i top-k suggerimenti in base alla frequenza, si aggiunga a ogni nodo un conteggio del numero di volte in cui è stata cercata la parola che termina in quel nodo. Durante la raccolta dei suggerimenti, si utilizzi un max-heap di dimensione k. In questo modo si riduce a O(k) il risultato della DFS, che avrebbe dimensione O(W), senza materializzare tutte le corrispondenze. I motori di ricerca reali combinano il percorso dei prefissi nel Trie con i dati sulla frequenza per fornire suggerimenti rapidi e pertinenti.

Implementare il Trie per LeetCode 208

LeetCode 208, «Implement Trie (Prefix Tree)», richiede esattamente: insert(word), search(word) che restituisce un booleano per la corrispondenza esatta e startsWith(prefix) che restituisce un booleano per la corrispondenza del prefisso. Questa è l'implementazione canonica di un Trie. Si ricordi: search richiede is_end=True; startsWith richiede soltanto che esista il percorso del prefisso.

class Trie:
    def __init__(self):
        self.root = {}
    
    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node:
                node[c] = {}
            node = node[c]
        node['#'] = True  # '#' marks word end
    
    def search(self, word):
        node = self.root
        for c in word:
            if c not in node: return False
            node = node[c]
        return '#' in node
    
    def startsWith(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node: return False
            node = node[c]
        return True

t = Trie()
t.insert('apple')
print(t.search('apple'))      # True
print(t.search('app'))        # False
print(t.startsWith('app'))   # True

Utilizzare '#' come marcatore di fine (Trie con dict)

Una scorciatoia elegante consiste nel memorizzare il Trie come dict annidati, usando una chiave sentinella speciale come '#' per contrassegnare la fine delle parole ed eliminando la necessità di una classe TrieNode. Questa soluzione è compatta e adatta ai colloqui, ma leggermente meno leggibile degli oggetti TrieNode espliciti. Entrambe le implementazioni sono accettabili; la versione con dict è più rapida da scrivere sotto pressione.

Prefisso comune più lungo con un Trie

Per trovare il prefisso comune più lungo di un elenco di stringhe, si inseriscano tutte le stringhe nel Trie, quindi si percorra la struttura dalla radice seguendo l'unico percorso esistente finché: (1) il nodo corrente ha esattamente un figlio e (2) is_end è False. Ci si fermi quando una delle due condizioni non è più soddisfatta. Il percorso seguito è il prefisso comune più lungo.

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

def longest_common_prefix(words):
    root = TrieNode()
    for word in words:
        node = root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    prefix = []
    node = root
    while len(node.children) == 1 and not node.is_end:
        char, node = next(iter(node.children.items()))
        prefix.append(char)
    return ''.join(prefix)

print(longest_common_prefix(['flower','flow','flight']))  # 'fl'
print(longest_common_prefix(['dog','racecar','car']))     # ''

Problema Replace Words

Replace Words (LeetCode 648): dato un dizionario di parole radice e una frase, si sostituisca ogni parola della frase con la radice corrispondente più breve presente nel dizionario. Si inseriscano tutte le radici in un Trie. Per ogni parola della frase, si percorra il Trie finché non viene trovata la fine di una radice, quindi si restituisca quella radice come sostituzione. Se nessuna radice corrisponde, si mantenga la parola originale. La complessità è O(total chars), rispetto a O(n × m) dell'approccio esaustivo.

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

def replaceWords(dictionary, sentence):
    root = TrieNode()
    for word in dictionary:
        node = root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def find_root(word):
        node = root
        for i, c in enumerate(word):
            if c not in node.children: break
            node = node.children[c]
            if node.is_end:
                return word[:i+1]
        return word
    
    return ' '.join(find_root(w) for w in sentence.split())

print(replaceWords(['cat','bat','rat'], 'the cattle was rattled by the battery'))

Problema Map Sum Pairs

Map Sum (LeetCode 677): inserire coppie chiave-valore e restituire la somma di tutti i valori le cui chiavi hanno un determinato prefisso. Si aggiunga a ogni TrieNode un campo val. Per l'inserimento, si percorra il Trie fino alla fine e si imposti il valore; per le query di somma, si percorra il Trie fino al nodo finale del prefisso e si calcoli con una DFS la somma di tutti i campi val sottostanti. In alternativa, si memorizzi in ogni nodo la somma cumulativa durante l'inserimento, ottenendo query in O(p).

Implementare il completamento automatico con risultati limitati

Nei sistemi di completamento automatico in produzione, restituire tutte le parole che iniziano con un prefisso non è pratico quando le corrispondenze sono migliaia. Si utilizza invece un max-heap di dimensione k durante l'attraversamento DFS: si mantengono le k parole con il punteggio più alto tra quelle trovate finora. Si interrompono in anticipo i rami DFS che non possono contenere una parola tra le prime k (potatura tramite un limite superiore del punteggio). In questo modo si ottiene O(p + k × log k) per query per k suggerimenti, un risultato molto migliore rispetto a raccogliere tutte le corrispondenze.

Verifica rapida

Verifichi la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep trattati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: starts_with attraversa il percorso del prefisso e restituisce True se questo esiste; non è necessario verificare is_end, autocomplete raccoglie tramite DFS tutte le parole a partire dal nodo finale del prefisso, aggiungendo i caratteri man mano che scende e arricchire i nodi con conteggi o valori consente di eseguire query di somma e ottenere suggerimenti tra i primi k risultati. Prossimamente aggiungeremo la ricerca con caratteri jolly ed espressioni regolari al trie.

Domande Frequenti

La lezione «Ricerca per prefisso e Starts-With» è gratuita?

Sì — il testo completo di «Ricerca per prefisso e Starts-With» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Ricerca per prefisso e Starts-With»?

Aggiunga un metodo starts_with che restituisca true se una parola inserita condivide un determinato prefisso e lo usi per implementare i suggerimenti di completamento automatico. Eserciti DSA Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare DSA Interview Prep?

Non è richiesta alcuna esperienza precedente. DSA Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Ricerca per prefisso e Starts-With»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione DSA Interview Prep?

Sì. Ogni lezione DSA Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Classe TrieNode: inserimento e ricerca
  2. Ricerca per prefisso e Starts-With
  3. Ricerca con caratteri jolly ed espressioni regolari in un trie
  4. Word Search II: trie e backtracking su griglia
← Torna a DSA Interview Prep