Omówienie trudnych problemów: Word Ladder II i Alien Dictionary
Rozwiązać od początku do końca dwa trudne problemy — word-ladder-II za pomocą BFS i przeszukiwania z nawrotami oraz alien-dictionary za pomocą sortowania topologicznego — wraz z pełnym wyjaśnieniem
Omówienie trudnych problemów: Word Ladder II i Alien Dictionary to bezpłatna lekcja Coding Interview Prep na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Coding Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.
Dlaczego trudne problemy są inne
Trudne problemy z LeetCode różnią się od średnio trudnych na dwa kluczowe sposoby: (1) wymagają połączenia co najmniej dwóch technik algorytmicznych oraz (2) optymalne rozwiązanie często nie wynika wprost z samej treści zadania — trzeba dostrzec ukrytą pod powierzchownym opisem strukturę grafu lub DP. Word Ladder II i Alien Dictionary to klasyczne trudne problemy, które regularnie pojawiają się na rozmowach rekrutacyjnych w firmach FAANG.
W przypadku trudnych problemów nie należy próbować od razu dostrzec kompletnego rozwiązania. Zamiast tego proszę podzielić problem na podproblemy, rozpoznać strukturę każdego z nich, rozwiązać je niezależnie, a następnie połączyć wyniki. Takie modułowe myślenie jest kluczem do rozwiązywania trudnych problemów pod presją.
# Hard problem meta-strategy
strategy = [
'1. Read the problem 2x — hard problems often have subtle constraints',
'2. Model it as a known structure: graph? DP table? sorted order?',
'3. Break into sub-problems: separate the graph-building from the traversal',
'4. Solve sub-problems in order, verifying each before connecting',
'5. Handle the edge case where no solution exists (empty result, -1, [])',
'6. Optimise only after the correct but slow solution works',
]
print('Hard problem meta-strategy:')
for step in strategy:
print(f' {step}')Word Ladder II: treść problemu
Word Ladder II (LeetCode 126): mając słowo początkowe, słowo końcowe i listę słów, należy znaleźć wszystkie najkrótsze sekwencje przekształceń prowadzące od początku do końca. W każdym kroku zmieniany jest dokładnie jeden znak, a każde słowo pośrednie musi znajdować się na liście słów. Jest to problem zdecydowanie trudniejszy niż Word Ladder I (w którym znajduje się tylko jedną najkrótszą ścieżkę), ponieważ trzeba wyliczyć wszystkie optymalne ścieżki.
Przykład: beginWord='hit', endWord='cog', wordList=['hot','dot','dog','lot','log','cog'] → [['hit','hot','dot','dog','cog'],['hit','hot','lot','log','cog']]. Obie ścieżki mają długość 5.
# Word Ladder II problem breakdown
begin_word = 'hit'
end_word = 'cog'
word_list = ['hot','dot','dog','lot','log','cog']
# What we need:
# 1. Build a graph: word -> set of words that differ by one character
# 2. BFS to find the MINIMUM number of steps (shortest path distance)
# 3. DFS/backtracking to enumerate ALL paths of that minimum length
# Key insight: BFS finds shortest distance; DFS reconstructs all shortest paths
# Two-phase approach:
print('Phase 1: BFS from begin_word to find min distance to each word')
print('Phase 2: DFS/backtrack from end_word using only edges that decrease distance')
print()
print(f'Input: {begin_word} -> {end_word}')
print(f'Word list: {word_list}')
print('Expected: [[hit,hot,dot,dog,cog],[hit,hot,lot,log,cog]]')Word Ladder II: faza BFS
W fazie 1 należy uruchomić BFS poziom po poziomie, zaczynając od słowa początkowego. Na każdym poziomie znajdujemy wszystkich sąsiadów (słowa różniące się jednym znakiem). Zapisujemy poziom (odległość od początku), na którym po raz pierwszy docieramy do każdego słowa. Nie kończymy działania po dotarciu do słowa końcowego — kontynuujemy je do końca poziomu, na którym znaleziono end_word, aby mieć pewność, że przeanalizowaliśmy wszystkie najkrótsze ścieżki.
Co najważniejsze, tworzymy słownik parents, który przypisuje każdemu słowu zbiór słów mogących je poprzedzać na dowolnej najkrótszej ścieżce. Jest to graf używany w fazie 2 do odtwarzania ścieżek metodą wyszukiwania wstecznego.
from collections import defaultdict, deque
def find_parents(begin, end, word_set):
parents = defaultdict(set)
layer = {begin}
found = False
while layer and not found:
next_layer = set()
for word in layer:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
new_word = word[:i] + c + word[i+1:]
if new_word in word_set and new_word not in parents:
next_layer.add(new_word)
parents[new_word].add(word)
if new_word == end:
found = True
layer = next_layer
return parents if found else {}
words = {'hot','dot','dog','lot','log','cog'}
parents = find_parents('hit', 'cog', words)
print('Parents map (which words can precede each word):')
for word, preds in sorted(parents.items()):
print(f' {word}: {preds}')Word Ladder II: faza wyszukiwania DFS wstecz
W fazie 2 należy zastosować wyszukiwanie DFS wstecz, zaczynając od słowa końcowego i poruszając się wstecz po mapie parents. Ścieżki budujemy od końca do początku, a następnie je odwracamy. Gdy docieramy do słowa początkowego, znaleźliśmy kompletną najkrótszą ścieżkę. Mapa rodziców gwarantuje, że wszystkie znalezione ścieżki mają minimalną długość — nie możemy „zboczyć” na dłuższą ścieżkę.
To dwuetapowe podejście (BFS do wyznaczania poziomów i DFS do odtwarzania ścieżek) jest standardowym rozwiązaniem. Jego złożoność dla BFS wynosi O(n × L × 26), gdzie n oznacza rozmiar listy słów, a L — długość słowa, oraz O(K × L) dla DFS, gdzie K oznacza liczbę najkrótszych ścieżek.
def find_ladders(beginWord, endWord, wordList):
word_set = set(wordList)
if endWord not in word_set:
return []
# Phase 1: BFS to build parents map
parents = defaultdict(set)
layer = {beginWord}
found = False
visited = {beginWord}
while layer and not found:
next_layer = set()
for word in layer:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
nw = word[:i] + c + word[i+1:]
if nw in word_set and nw not in visited:
next_layer.add(nw)
parents[nw].add(word)
if nw == endWord: found = True
visited |= next_layer
layer = next_layer
# Phase 2: DFS backtrack from endWord to beginWord
result = []
def dfs(word, path):
if word == beginWord:
result.append(path[::-1])
return
for parent in parents[word]:
dfs(parent, path + [parent])
dfs(endWord, [endWord])
return result
print(find_ladders('hit','cog',['hot','dot','dog','lot','log','cog']))Alien Dictionary: treść problemu
Alien Dictionary (LeetCode 269): mając listę słów uporządkowanych leksykograficznie w obcym języku, należy ustalić kolejność znaków w tym języku. Należy zwrócić uporządkowanie znaków w postaci ciągu. Jeśli nie istnieje poprawne uporządkowanie (występują sprzeczności), należy zwrócić pusty ciąg.
Przykład: ['wrt','wrf','er','ett','rftt'] → 'wertf'. Porównując sąsiednie słowa, otrzymujemy: 't' < 'f' (z porównania wrt i wrf), 'w' < 'e' (z porównania wrt i er), 'r' < 't' (z porównania er i ett) oraz 'e' < 'r' (z porównania ett i rftt). Jest to sortowanie topologiczne ograniczeń określających kolejność znaków.
words = ['wrt', 'wrf', 'er', 'ett', 'rftt']
# Compare adjacent pairs to extract ordering:
# wrt vs wrf: first diff at index 2: t < f (t comes before f)
# wrf vs er: first diff at index 0: w < e (w comes before e)
# er vs ett: first diff at index 1: r < t (r comes before t)
# ett vs rftt:first diff at index 0: e < r (e comes before r)
ordering_constraints = [
('t', 'f', 'from wrt vs wrf'),
('w', 'e', 'from wrf vs er'),
('r', 't', 'from er vs ett'),
('e', 'r', 'from ett vs rftt'),
]
print('Ordering constraints extracted from adjacent word pairs:')
for a, b, source in ordering_constraints:
print(f' {a} -> {b} ({source})')
print('\nThis is a directed graph: find topological order = alien alphabet order')Alien Dictionary: budowanie grafu
Pierwszym krokiem jest wyodrębnienie ograniczeń: należy porównać każdą sąsiednią parę słów, znaleźć pierwszy różniący je znak i dodać skierowaną krawędź od znaku mniejszego do większego. Jeśli jedno słowo jest prefiksem następnego, ale jest od niego dłuższe (na przykład „abc” występuje przed „ab”), dane wejściowe są niepoprawne — należy natychmiast zwrócić pusty ciąg.
Wszystkie znaki występujące na liście słów są wierzchołkami grafu, nawet jeśli nie dotyczą ich żadne ograniczenia kolejności. Takie izolowane wierzchołki mogą znaleźć się w dowolnym miejscu końcowego uporządkowania.
from collections import defaultdict
def build_alien_graph(words):
adj = defaultdict(set) # char -> set of chars that come after it
in_degree = {c: 0 for word in words for c in word}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i+1]
min_len = min(len(w1), len(w2))
found_diff = False
for j in range(min_len):
if w1[j] != w2[j]:
if w2[j] not in adj[w1[j]]: # avoid duplicate edges
adj[w1[j]].add(w2[j])
in_degree[w2[j]] += 1
found_diff = True
break
if not found_diff and len(w1) > len(w2):
return {}, {} # invalid: 'abc' before 'ab'
return adj, in_degree
words = ['wrt', 'wrf', 'er', 'ett', 'rftt']
adj, in_degree = build_alien_graph(words)
print('Adjacency list (directed):', {k: list(v) for k, v in adj.items()})
print('In-degrees:', in_degree)Alien Dictionary: sortowanie topologiczne
Po zbudowaniu grafu należy zastosować sortowanie topologiczne BFS algorytmem Kahna: zainicjalizować kolejkę wszystkimi znakami o stopniu wejściowym równym 0 (bez poprzedników). Następnie przetwarzać każdy znak i zmniejszać stopień wejściowy jego następników. Gdy stopień wejściowy następnika osiągnie 0, należy dodać go do kolejki. Znaki zebrane w kolejności przetwarzania tworzą alfabetyczną kolejność obcego języka.
Jeśli wynik zawiera wszystkie znaki, uporządkowanie jest poprawne. Jeśli zawiera mniej znaków, niż oczekiwano, graf zawiera cykl — ograniczenia są sprzeczne i należy zwrócić pusty ciąg.
from collections import deque, defaultdict
def alien_order(words):
adj = defaultdict(set)
in_degree = {c: 0 for word in words for c in word}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i + 1]
min_len = min(len(w1), len(w2))
found = False
for j in range(min_len):
if w1[j] != w2[j]:
if w2[j] not in adj[w1[j]]:
adj[w1[j]].add(w2[j])
in_degree[w2[j]] += 1
found = True; break
if not found and len(w1) > len(w2):
return '' # invalid: 'abc' before 'ab'
# Kahn's BFS topological sort
queue = deque([c for c in in_degree if in_degree[c] == 0])
result = []
while queue:
c = queue.popleft()
result.append(c)
for neighbor in sorted(adj[c]): # sort for determinism
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return ''.join(result) if len(result) == len(in_degree) else ''
print(alien_order(['wrt','wrf','er','ett','rftt'])) # e.g., 'wertf'
print(alien_order(['z','x'])) # 'zx'
print(alien_order(['z','x','z'])) # '' (cycle z->x->z)Obsługa przypadków brzegowych: oba problemy
Word Ladder II i Alien Dictionary mają subtelne przypadki brzegowe, które prowadzą do błędnych odpowiedzi, jeśli nie zostaną obsłużone:
- Word Ladder II: beginWord i endWord są takie same (należy zwrócić
[[beginWord]]lub ścieżkę o długości 1). endWord nie występuje na wordList (należy zwrócić pusty wynik). Nie istnieje żadna ścieżka (należy zwrócić pusty wynik). - Alien Dictionary: zduplikowane słowa (nie wyodrębniamy żadnego ograniczenia). Jedno słowo (zwracamy wszystkie unikalne znaki). Cykl w ograniczeniach (zwracamy ''). Jedno słowo jest dłuższym prefiksem następnego (niepoprawne dane wejściowe, zwracamy ''). Wszystkie znaki są izolowane (zwracamy dowolną kolejność).
# Edge case tests for Word Ladder II
def test_word_ladder_edge_cases():
from collections import defaultdict
def find_ladders(begin, end, word_list):
# [abbreviated implementation for testing]
if end not in word_list: return []
if begin == end: return [[begin]]
return [] # placeholder
tests = [
('hit', 'cog', ['hot','dot','dog','lot','log'], []), # no path (cog missing)
('hit', 'hit', ['hit'], [['hit']]), # begin==end
('a', 'c', ['a','b','c'], [['a','c']]), # short words
]
for begin, end, wl, expected in tests:
result = find_ladders(begin, end, wl)
print(f'{begin}->{end}: result={result}')
# Edge case tests for Alien Dictionary
def test_alien_edge_cases():
from collections import defaultdict, deque
# (using alien_order from previous scene)
tests = [
(['abc', 'ab'], ''), # 'abc' before 'ab' = invalid
(['a'], 'a'), # single word
(['z','z'], 'z'), # duplicate: no constraint
]
print('Alien dictionary edge cases:')
for words, expected in tests:
print(f' {words} -> expected: "{expected}"')
test_word_ladder_edge_cases()
test_alien_edge_cases()Analiza złożoności: oba problemy
Złożoność Word Ladder II: faza BFS ma złożoność O(n × L × 26), gdzie n oznacza liczbę słów na liście, a L — długość słowa. Dla każdego słowa na każdym poziomie BFS generujemy 26L słów kandydujących i sprawdzamy ich obecność w zbiorze słów (każde sprawdzenie ma złożoność O(1)). Faza DFS ma złożoność O(K × L), gdzie K oznacza liczbę najkrótszych ścieżek (teoretycznie może być ona wykładnicza).
Złożoność Alien Dictionary: budowanie grafu ma złożoność O(C), gdzie C oznacza łączną liczbę znaków we wszystkich słowach. Sortowanie topologiczne ma złożoność O(V + E), gdzie V oznacza liczbę unikalnych znaków, a E — liczbę ograniczeń kolejności. Łącznie otrzymujemy O(C), czyli O(łącznej liczby znaków na wejściu).
# Complexity analysis for both problems
complexities = [
{
'problem': 'Word Ladder II',
'time': 'O(n * L * 26) BFS + O(K * L) DFS backtracking',
'space': 'O(n * L) for word set + parents map',
'notes': 'K (number of shortest paths) can be exponential in pathological cases',
},
{
'problem': 'Alien Dictionary',
'time': 'O(C) where C = total characters in all words',
'space': 'O(V + E) for adjacency list',
'notes': 'V <= 26 (alphabet), E <= V^2 = 676; often treated as O(C) total',
},
]
for c in complexities:
print(f'{c["problem"]}:')
print(f' Time: {c["time"]}')
print(f' Space: {c["space"]}')
print(f' Notes: {c["notes"]}')
print()Podsumowanie schematów: dwa uniwersalne szablony
Oba problemy uczą uniwersalnych schematów. Word Ladder II = BFS do wyznaczania odległości + DFS do odtwarzania ścieżek: ten schemat pojawia się zawsze, gdy potrzebne są wszystkie najkrótsze ścieżki w grafie nieważonym. Mapę rodziców budujemy podczas BFS, a następnie wykonujemy wyszukiwanie wstecz od celu do źródła.
Alien Dictionary = wyodrębnianie krawędzi + sortowanie topologiczne: ten schemat pojawia się, gdy otrzymujemy uporządkowaną sekwencję i musimy wywnioskować leżące u jej podstaw reguły kolejności. Wyodrębniamy skierowane ograniczenia z sąsiednich par, a następnie stosujemy algorytm Kahna. W przypadku wykrycia cyklu zwracamy '' (uporządkowanie jest niemożliwe).
# Pattern templates
print('Template 1: All Shortest Paths in Unweighted Graph')
template_1 = '''
1. BFS from source, recording parents[node] = set of nodes that lead to node
2. Continue each BFS level fully (do not stop at first endNode reach)
3. DFS backtrack from endNode, following parents map
4. Reverse each path found (built end->start, need start->end)
'''
print(template_1)
print('Template 2: Infer Ordering from Sorted Sequence')
template_2 = '''
1. Compare adjacent pairs, extract first differing element as directed constraint
2. Build adjacency list + in-degree map
3. Check for invalid input (prefix longer than successor)
4. Kahn's BFS topological sort
5. If result length < number of nodes => cycle => return invalid
'''
print(template_2)Budowanie pewności siebie w rozwiązywaniu trudnych problemów
Na początku trudne problemy wydają się niemożliwe, ale właściwy model myślowy sprawia, że można podejść do nich metodycznie. Kluczowe spostrzeżenia:
- Rozdzielaj zadania: rozwiązuj każdy podproblem niezależnie, zanim je połączysz
- Znaj swoje podstawowe narzędzia: BFS/DFS, sortowanie topologiczne, algorytm Dijkstry, tablice DP — trudne problemy łączą je w nieoczywisty sposób
- Zacznij od przykładów: prześledź problem ręcznie na małym przykładzie, aby odkryć jego ukrytą strukturę
- Weryfikuj podproblemy: po zaimplementowaniu fazy 1 (budowania grafu) wyświetl graf i ręcznie sprawdź jego poprawność przed przejściem do fazy 2
# Hard problem confidence-building practice plan
practice_plan = [
('Week 1', 'BFS/DFS fundamentals', ['Number of Islands', 'Clone Graph', 'Word Ladder I']),
('Week 2', 'Topological sort', ['Course Schedule I & II', 'Alien Dictionary (easy)']),
('Week 3', 'All-paths problems', ['All Paths to Target', 'Word Ladder II (hard)']),
('Week 4', 'Hard combos', ['Minimum Window Substring', 'Serialize/Deserialize Tree']),
]
print('4-week hard problem practice plan:')
for week, theme, problems in practice_plan:
print(f'\n{week} — {theme}:')
for p in problems:
print(f' - {p}')
print('\nAfter each problem, write:')
print(' 1. The pattern it belongs to')
print(' 2. The 2-3 key sub-problems')
print(' 3. One insight you would not have had before solving it')Szybki sprawdzian
Sprawdź swoje rozumienie zagadnień Data Structures & Algorithms — Coding Interview Prep z tej lekcji.
Podsumowanie lekcji
W tej lekcji nauczył(a) się Pan/Pani, że: Word Ladder II używa BFS do zbudowania mapy parents wszystkich poprzedników najkrótszych ścieżek, a następnie wyszukiwania DFS wstecz do wyliczenia wszystkich najkrótszych ścieżek przez przechodzenie po parents od końca do początku, Alien Dictionary wyodrębnia skierowane ograniczenia z sąsiednich par słów i stosuje sortowanie topologiczne Kahna do uporządkowania znaków, zwracając pusty ciąg po wykryciu cyklu, a także trudne problemy można podzielić na wiele podproblemów — budowanie grafu, wyznaczanie odległości i odtwarzanie ścieżek — z których każdy rozwiązuje się niezależnie za pomocą znanych algorytmów. Ukończył(a) już Pan/Pani cały kurs DSA Interview Prep. Proszę z pewnością stosować wszystkie poznane schematy i techniki podczas rozmów rekrutacyjnych.
Często zadawane pytania
Czy lekcja „Omówienie trudnych problemów: Word Ladder II i Alien Dictionary” jest bezpłatna?
Tak — pełny tekst „Omówienie trudnych problemów: Word Ladder II i Alien Dictionary” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Coding Interview Prep, przejdź na CoddyKit PRO. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Omówienie trudnych problemów: Word Ladder II i Alien Dictionary”?
Rozwiązać od początku do końca dwa trudne problemy — word-ladder-II za pomocą BFS i przeszukiwania z nawrotami oraz alien-dictionary za pomocą sortowania topologicznego — wraz z pełnym wyjaśnieniem Ćwiczysz Coding Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Coding Interview Prep?
Nie wymagamy żadnego doświadczenia. Coding Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Omówienie trudnych problemów: Word Ladder II i Alien Dictionary”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Coding Interview Prep?
Tak. Każda lekcja Coding Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Ściągawka rozpoznawania wzorców
- Próbna rozmowa techniczna na czas: problemy łatwe i średnie
- Przypadki brzegowe i komunikacja podczas rozmowy technicznej
- Omówienie trudnych problemów: Word Ladder II i Alien Dictionary