Förberedelse inför kodningsintervjuer · Lektion

Word Search II: trie och backtracking i ett rutnät

Infoga alla målord i ett trie och kör DFS-backtracking på ett 2D-bräde för att hitta alla giltiga ord samtidigt på O(m × n × 4^L).

Lektion 4 av 413 steg

Word Search II: trie och backtracking i ett rutnät är en gratis lektion i Förberedelse inför kodningsintervjuer på CoddyKit. Detta är lektion 4 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Förberedelse inför kodningsintervjuer, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.

Problemet med Word Search II

Word Search II (LeetCode 212): givet ett teckenbräde med storleken m × n och en lista med ord ska du hitta alla ord som kan bildas av sekventiellt intilliggande celler (horisontellt eller vertikalt), där varje cell endast får användas en gång. Detta är svårare än Word Search I (ett enda ord), eftersom vi måste hitta alla matchande ord samtidigt — att naivt köra Word Search I för varje ord ger O(W × m × n × 4^L), vilket är för långsamt.

Varför trie + backtracking?

Genom att infoga alla målord i ett trie och sedan köra DFS-backtracking på brädet kan vi söka efter alla ord samtidigt. Vid varje brädcell kontrollerar vi i stället för ”bildar den här vägen mitt målord?” om ”matchar den här vägen ett prefix i trien?”. Så snart ett trie-prefix inte längre matchar beskär vi hela DFS-grenen — och undviker därmed överflödigt arbete för alla ord som delar prefix.

Bygga trien från en ordlista

Infoga alla ord i ett trie. Lagra hela ordet i lövnoden (i node.word) i stället för bara en boolesk flagga, så att vi omedelbart kan lägga till ordet i resultatet när en fullständig träff hittas under backtracking, utan att återskapa det tecken för tecken.

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

DFS-backtracking på rutnätet

Starta en DFS från varje cell på brädet. Gör följande vid varje steg: (1) kontrollera om den aktuella cellens tecken finns som ett barn i den aktuella trienoden; (2) om ja, markera cellen som besökt (sätt den till ett platshållartecken som '#'), gå rekursivt igenom de 4 grannarna; (3) återställ cellen efter rekursionen (avmarkera den). När en trienod har ett värde som inte är None i word, lägger du till det i resultatet och sätter det till None för att undvika dubbletter.

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

Komplexitetsanalys

Tid: O(m × n × 4^L), där L är den maximala ordlängden. För var och en av de m×n startcellerna utforskar DFS upp till 4^L sökvägar. Trien beskär sökvägar som inte matchar något ordprefix, så i praktiken går det mycket snabbare. Att bygga trien är O(W × L), där W är antalet ord. Utrymme: O(W × L) för trien samt O(L) för rekursionsstackens djup.

Beskärning: ta bort lövnoder efter en träff

Efter att ett ord hittats bör du ta bort lövnoden från trien (inte bara sätta word till null) om den saknar barn. Det förhindrar att döda grenar besöks igen i efterföljande DFS-anrop. När en nods children blir tomma efter att ordet hittats tar du bort noden ur dess förälders children-dict. Denna optimering är betydelsefull när många ord delar långa prefix.

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

Varför det är bättre att lagra word i noden

Att lagra hela ordet i trie-lövnoden (i stället för att återskapa det från DFS-sökvägen) har två fördelar: (1) ordet kan hämtas i O(1) när en träff hittas, i stället för att återskapa sökvägen i O(L); (2) att sätta node.word = None efter att ordet hittats ger en ren deduplicering i O(1), utan behov av en separat resultatmängd. För Word Search II är det särskilt viktigt att förhindra dubbletter, eftersom samma ord i teorin kan hittas via olika sökvägar.

Markera besökta celler på plats

I stället för en separat visited-mängd, som skulle kräva O(m × n) utrymme per DFS-sökväg, markerar vi celler direkt genom att ersätta deras tecken med ett platshållartecken som '#'. När DFS returnerar återställer vi det ursprungliga tecknet. Tekniken: (1) använder O(1) extra utrymme per cell; (2) förhindrar automatiskt att cellen besöks igen under samma sökväg; (3) är helt transparent för trie-traverseringen eftersom '#' aldrig finns i trien.

Specialfall att hantera

Viktiga specialfall: (1) duplicerade ord i ordlistan — lagra orden i en mängd, eller använd node.word = None-tricket för att förhindra dubbletter i resultatet; (2) mycket långa ord som överskrider brädets dimensioner — de kan inte bildas, men DFS hanterar detta naturligt genom att de intilliggande cellerna tar slut; (3) bräde med en enda cell — endast ord med ett tecken kan hittas; (4) samma ord kan hittas via olika sökvägar — node.word = None-tricket förhindrar dubbelräkning.

Jämförelse med den naiva metoden

Naiv metod: kör Word Search I för vart och ett av W ord: O(W × m × n × 4^L). Med trien söks alla ord samtidigt: O(m × n × 4^L), oavsett W. För W=1000 ord med längden 10 på ett bräde med storleken 10×10 är den naiva metoden 1000× långsammare än trie-metoden. Trien fungerar som ett gemensamt prefixfilter som fördelar kostnaden över alla ord — ett klassiskt exempel på hur en datastruktur kan ge en asymptotisk förbättring.

Sammanfattning av hela lösningen

Fullständig lösning på Word Search II: bygg ett trie med orden och lagra ordsträngen i lövnoden. Kör DFS från varje brädcell: kontrollera om det aktuella tecknet finns i den aktuella trienoden, markera cellen som '#', gå rekursivt igenom de 4 grannarna och återställ cellen. När node.word inte är null lägger du till det i resultatet och nollar det. Du kan även välja att beskära tomma triegrenar efter användning. Returnera resultatlistan. Tid: O(m×n×4^L), utrymme: O(W×L) för trien + O(L) rekursion.

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 result

Snabbkontroll

Testa dina kunskaper om koncepten i Data Structures & Algorithms — Coding Interview Prep från den här lektionen.

Sammanfattning av lektionen

I den här lektionen lärde du dig: Word Search II använder ett trie för samtidig sökning efter flera ord med gemensam prefixbeskärning, att lagra ordsträngen i trie-lövet möjliggör O(1)-hämtning av ordet och enkel deduplicering genom att sätta det till None efter att ordet hittats, och markering av besökta celler på plats med '#' undviker O(m×n) extra utrymme per DFS-sökväg. Detta avslutar kursen Tries and String Algorithms — du har bemästrat en av de mest kraftfulla strängspecifika datastrukturerna som används i intervjuer.

Gratis att börja

Lär dig Förberedelse inför kodningsintervjuer med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
90
Lektioner
360

Vanliga frågor

Är lektionen ”Word Search II: trie och backtracking i ett rutnät” gratis?

Ja – hela texten till ”Word Search II: trie och backtracking i ett rutnät” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Förberedelse inför kodningsintervjuer, kan Ni uppgradera till CoddyKit PRO. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.

Vad lär jag mig i ”Word Search II: trie och backtracking i ett rutnät”?

Infoga alla målord i ett trie och kör DFS-backtracking på ett 2D-bräde för att hitta alla giltiga ord samtidigt på O(m × n × 4^L). Ni övar på Förberedelse inför kodningsintervjuer med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Förberedelse inför kodningsintervjuer?

Du behöver inga förkunskaper. Utbildningen i Förberedelse inför kodningsintervjuer på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.

Hur lång tid tar lektionen ”Word Search II: trie och backtracking i ett rutnät”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Förberedelse inför kodningsintervjuer-lektionen?

Ja. Varje Förberedelse inför kodningsintervjuer-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. TrieNode-klassen: infogning och sökning
  2. Prefixsökning och Starts-With
  3. Wildcard- och regexsökning i ett trie
  4. Word Search II: trie och backtracking i ett rutnät
← Tillbaka till Förberedelse inför kodningsintervjuer