Word Search II: trie e backtracking su griglia
Inserisca tutte le parole obiettivo in un trie ed esegua il backtracking DFS su una griglia 2D per trovare simultaneamente tutte le parole valide in O(m × n × 4^L).
Word Search II: trie e backtracking su griglia è una lezione DSA Interview Prep gratuita su CoddyKit. Questa è la lezione 4 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.
Il problema Word Search II
Word Search II (LeetCode 212): dato un tabellone di caratteri m × n e un elenco di parole, trovare tutte le parole che possono essere formate da celle adiacenti in sequenza (orizzontalmente o verticalmente), dove ogni cella può essere utilizzata una sola volta. È più difficile di Word Search I (una sola parola), perché è necessario trovare tutte le parole corrispondenti simultaneamente: eseguire ingenuamente Word Search I per ogni parola ha complessità O(W × m × n × 4^L), troppo lenta.
Perché usare trie e backtracking?
Inserire tutte le parole cercate in un trie e poi eseguire il backtracking DFS sul tabellone consente di cercare tutte le parole simultaneamente. In ogni cella del tabellone, invece di verificare «questo percorso forma la mia parola cercata?», si verifica «questo percorso corrisponde a un prefisso nel trie?». Non appena un prefisso del trie non corrisponde, si pota l'intero ramo DFS, evitando lavoro ridondante tra tutte le parole che condividono quel prefisso.
Costruire il trie dall'elenco di parole
Si inseriscano tutte le parole in un trie. Si memorizzi la parola completa nel nodo foglia (in node.word) anziché solo un valore booleano, così, quando durante il backtracking viene trovata una corrispondenza completa, è possibile aggiungere immediatamente la parola ai risultati senza ricostruirla carattere per carattere.
class TrieNode:
def __init__(self):
self.children = {}
self.word = None # stores the complete word if this is an end node
def build_trie(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.word = word # mark complete word here
return root
root = build_trie(['eat','oath','ot'])
print('Trie built with', len(root.children), 'root children')Backtracking DFS sulla griglia
Si avvii una DFS da ogni cella del tabellone. A ogni passo: (1) si verifichi se il carattere della cella corrente esiste come figlio nel nodo corrente del trie; (2) in caso affermativo, si contrassegni la cella come visitata (impostandola su un sentinella come '#') e si ricorra sui 4 vicini; (3) dopo la ricorsione, si ripristini la cella (rimuovendo il contrassegno). Quando un nodo del trie ha un word diverso da None, lo si aggiunga ai risultati e lo si imposti a None per evitare duplicati.
class TrieNode:
def __init__(self):
self.children = {}
self.word = None
def findWords(board, 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.word = word
m, n = len(board), len(board[0])
result = []
def dfs(i, j, node):
c = board[i][j]
if c not in node.children:
return
next_node = node.children[c]
if next_node.word:
result.append(next_node.word)
next_node.word = None # avoid duplicates
board[i][j] = '#' # mark visited
for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
ni, nj = i+di, j+dj
if 0<=ni<m and 0<=nj<n and board[ni][nj] != '#':
dfs(ni, nj, next_node)
board[i][j] = c # restore
for i in range(m):
for j in range(n):
dfs(i, j, root)
return result
board = [['o','a','a','n'],['e','t','a','e'],['i','h','k','r'],['i','f','l','v']]
words = ['oath','pea','eat','rain']
print(findWords(board, words)) # ['oath','eat']Analisi della complessità
Tempo: O(m × n × 4^L), dove L è la lunghezza massima delle parole. Per ciascuna delle m×n celle iniziali, la DFS esplora fino a 4^L percorsi. Il trie pota i percorsi che non corrispondono ad alcun prefisso di parola, quindi nella pratica è molto più veloce. La costruzione del trie è O(W × L), dove W è il numero di parole. Spazio: O(W × L) per il trie, oltre a una profondità dello stack di ricorsione pari a O(L).
Potatura: rimuovere i nodi foglia dopo aver trovato una parola
Dopo aver trovato una parola, si rimuova il nodo foglia dal trie (non ci si limiti a impostare la parola a null) se non ha figli. Questo impedisce di attraversare nuovamente rami inattivi nelle chiamate DFS successive. Quando i figli di un nodo diventano vuoti dopo il ritrovamento della parola, si rimuova il nodo dal dizionario dei figli del genitore. Questa ottimizzazione è significativa quando molte parole condividono prefissi lunghi.
def dfs_with_pruning(i, j, node, board, m, n, result):
c = board[i][j]
if c not in node.children:
return
next_node = node.children[c]
if next_node.word:
result.append(next_node.word)
next_node.word = None
board[i][j] = '#'
for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
ni, nj = i+di, j+dj
if 0<=ni<m and 0<=nj<n and board[ni][nj] != '#':
dfs_with_pruning(ni, nj, next_node, board, m, n, result)
board[i][j] = c
# Prune: if the node has no more children and no word, remove it
if not next_node.children and not next_node.word:
del node.children[c]
print('Leaf pruning removes exhausted trie branches during search')Perché memorizzare word nel nodo è meglio
Memorizzare la parola completa nel nodo foglia del trie (anziché ricostruirla dal percorso DFS) offre due vantaggi: (1) recupero della parola in O(1) quando viene trovata una corrispondenza, invece della ricostruzione del percorso in O(L); (2) impostare node.word = None dopo aver trovato la parola consente una deduplicazione pulita in O(1), senza bisogno di un set dei risultati separato. In Word Search II, in particolare, impedire i duplicati è importante perché in teoria la stessa parola potrebbe essere trovata seguendo percorsi diversi.
Contrassegnare le celle visitate direttamente nella griglia
Anziché utilizzare un set visited separato (che richiederebbe spazio O(m × n) per ogni percorso DFS), si contrassegnano le celle direttamente nella griglia sostituendo il loro carattere con un sentinella come '#'. Al termine della DFS, si ripristina il carattere originale. Questa tecnica: (1) utilizza O(1) di spazio aggiuntivo per cella; (2) impedisce automaticamente di visitare nuovamente una cella all'interno dello stesso percorso; (3) è completamente trasparente all'attraversamento del trie, poiché '#' non sarà mai presente nel trie.
Casi limite da gestire
Casi limite importanti: (1) parole duplicate nell'elenco: memorizzarle in un set oppure utilizzare il trucco node.word = None per impedire duplicati nei risultati; (2) parole molto lunghe che superano le dimensioni del tabellone: non possono essere formate, ma la DFS gestisce naturalmente il caso quando esaurisce le celle adiacenti; (3) tabellone a cella singola: è possibile trovare solo parole di un carattere; (4) stessa parola trovabile attraverso percorsi diversi: il trucco node.word = None impedisce di conteggiarla due volte.
Confronto con l'approccio ingenuo
Approccio ingenuo: per ciascuna delle W parole, eseguire Word Search I: O(W × m × n × 4^L). Con il trie, tutte le parole vengono cercate simultaneamente: O(m × n × 4^L), indipendentemente da W. Per W=1000 parole di lunghezza 10 su un tabellone 10×10, l'approccio ingenuo è 1000 volte più lento rispetto al trie. Il trie agisce da filtro condiviso sui prefissi, ammortizzando il costo tra tutte le parole: un esempio classico di utilizzo di una struttura dati per ottenere un miglioramento asintotico.
Riepilogo della soluzione completa
Soluzione completa di Word Search II: costruire il trie con le parole e memorizzare la stringa della parola nella foglia. Per ogni cella del tabellone, eseguire una DFS: verificare se il carattere corrente esiste nel nodo corrente del trie, contrassegnare la cella con '#', ricorrere sui 4 vicini e ripristinare la cella. Quando node.word non è null, aggiungerlo ai risultati e impostarlo a null. Facoltativamente, potare i rami vuoti del trie dopo l'utilizzo. Restituire l'elenco dei risultati. Tempo: O(m×n×4^L), spazio: trie O(W×L) + ricorsione O(L).
class TrieNode:
def __init__(self):
self.children = {}
self.word = None
def findWords_final(board, words):
root = TrieNode()
for word in words:
node = root
for c in word:
node = node.children.setdefault(c, TrieNode())
node.word = word
m, n = len(board), len(board[0])
result = []
def dfs(i, j, node):
c = board[i][j]
child = node.children.get(c)
if not child:
return
if child.word:
result.append(child.word)
child.word = None
board[i][j] = '#'
for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
ni, nj = i+di, j+dj
if 0<=ni<m and 0<=nj<n and board[ni][nj] != '#':
dfs(ni, nj, child)
board[i][j] = c
if not child.children:
del node.children[c]
for i in range(m):
for j in range(n):
dfs(i, j, root)
return resultVerifica 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: Word Search II utilizza un trie per cercare simultaneamente più parole, potando i prefissi condivisi, memorizzare la stringa della parola nella foglia del trie consente di recuperarla in O(1) e di deduplicare facilmente impostandola a None dopo averla trovata e contrassegnare direttamente le celle visitate con '#' evita O(m×n) di spazio aggiuntivo per ogni percorso DFS. Con questa lezione si conclude il corso Tries and String Algorithms: ha acquisito padronanza di una delle strutture dati specifiche per le stringhe più potenti e utilizzate nei colloqui tecnici.
Domande Frequenti
La lezione «Word Search II: trie e backtracking su griglia» è gratuita?
Sì — il testo completo di «Word Search II: trie e backtracking su griglia» è 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 «Word Search II: trie e backtracking su griglia»?
Inserisca tutte le parole obiettivo in un trie ed esegua il backtracking DFS su una griglia 2D per trovare simultaneamente tutte le parole valide in O(m × n × 4^L). 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 4 di 4.
Quanto tempo richiede la lezione «Word Search II: trie e backtracking su griglia»?
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
- Classe TrieNode: inserimento e ricerca
- Ricerca per prefisso e Starts-With
- Ricerca con caratteri jolly ed espressioni regolari in un trie
- Word Search II: trie e backtracking su griglia