Разбор сложных задач: «Лестница слов II» и «Инопланетный словарь»
Решите две сложные задачи от начала до конца — «Лестницу слов II» с BFS и перебором с возвратом и «Инопланетный словарь» с топологической сортировкой — с подробным объяснением
«Разбор сложных задач: «Лестница слов II» и «Инопланетный словарь»» — бесплатный урок DSA Interview Prep на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DSA Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DSA Interview Prep содержит 4 уроков всего.
Чем сложные задачи отличаются
Сложные задачи LeetCode отличаются от задач средней сложности двумя ключевыми особенностями: (1) они требуют объединения двух или более алгоритмических методов и (2) оптимальное решение часто не очевидно из одного условия задачи — необходимо увидеть за поверхностным описанием лежащую в основе структуру графа или DP. «Лестница слов II» и «Инопланетный словарь» — классические сложные задачи, которые регулярно встречаются на собеседованиях в FAANG.
Подход к сложным задачам таков: не пытайтесь сразу увидеть полное решение. Вместо этого разбейте задачу на подзадачи, определите структуру каждой подзадачи, решите их независимо, а затем соедините решения. Такое модульное мышление — ключ к решению сложных задач в условиях давления.
# 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}')«Лестница слов II»: условие задачи
«Лестница слов II» (LeetCode 126): даны начальное слово, конечное слово и список слов; найдите все кратчайшие последовательности преобразований от начального слова к конечному. На каждом шаге изменяется ровно один символ, а каждое промежуточное слово должно входить в список слов. Эта задача значительно сложнее задачи «Лестница слов I», где требуется найти только один кратчайший путь, поскольку здесь необходимо перечислить все оптимальные пути.
Пример: beginWord='hit', endWord='cog', wordList=['hot','dot','dog','lot','log','cog'] → [['hit','hot','dot','dog','cog'],['hit','hot','lot','log','cog']]. Длина каждой последовательности равна 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]]')«Лестница слов II»: этап BFS
На этапе 1 выполните BFS по уровням, начиная с начального слова. На каждом уровне найдите всех соседей — слова, отличающиеся одним символом. Запишите уровень (расстояние от начального слова), на котором каждое слово было достигнуто впервые. Мы не останавливаемся при достижении конечного слова (NOT): продолжаем поиск до конца уровня, на котором оно было найдено, чтобы исследовать все кратчайшие пути.
Ключевой момент: мы строим словарь parents, сопоставляющий каждому слову множество слов, которые могут предшествовать ему в любом кратчайшем пути. Это граф, который используется на этапе 2 для обратного прохода.
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}')«Лестница слов II»: этап обратного прохода DFS
На этапе 2 выполните DFS с возвратом, начиная с конечного слова и двигаясь в обратном направлении по карте parents. Мы строим пути от конца к началу, а затем разворачиваем их. Достигнув начального слова, мы нашли полный кратчайший путь. Карта предшественников гарантирует, что все найденные пути имеют минимальную длину: мы не можем «отклониться» на более длинный путь.
Этот двухэтапный подход (BFS для определения уровней и DFS для восстановления путей) является стандартным решением. Его сложность для BFS составляет O(n × L × 26), где n — размер списка слов, а L — длина слова, плюс O(K × L) для DFS, где K — количество кратчайших путей.
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']))«Инопланетный словарь»: условие задачи
«Инопланетный словарь» (LeetCode 269): дан список слов, отсортированных лексикографически на инопланетном языке. Определите порядок символов в этом языке. Верните порядок символов в виде строки. Если допустимого порядка не существует из-за противоречий, верните пустую строку.
Пример: ['wrt','wrf','er','ett','rftt'] → 'wertf'. Сравнивая соседние слова, получаем: 't' < 'f' (из wrt и wrf), 'w' < 'e' (из wrt и er), 'r' < 't' (из er и ett), 'e' < 'r' (из ett и rftt). Это топологическая сортировка ограничений на порядок символов.
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')«Инопланетный словарь»: построение графа
Первый шаг — извлечение ограничений: сравните каждую соседнюю пару слов, найдите первый различающийся символ и выполните add ориентированного ребра от меньшего символа к большему. Если одно слово является префиксом следующего, но длиннее него (например, «abc» стоит перед «ab»), входные данные некорректны — немедленно верните пустую строку.
Все символы, встречающиеся в списке слов, являются вершинами графа, даже если для них не задано ни одного ограничения порядка. Такие изолированные вершины могут находиться в любом месте итогового порядка.
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)«Инопланетный словарь»: топологическая сортировка
После построения графа примените топологическую сортировку BFS по алгоритму Кана: инициализируйте очередь всеми символами с входной степенью 0, то есть без предварительных условий. Обрабатывайте каждый символ и уменьшайте входную степень его последователей. Когда входная степень последователя становится равной 0, добавляйте его в очередь. Собирайте символы в порядке обработки — это и будет алфавитный порядок инопланетного языка.
Если результат содержит все символы, порядок корректен. Если символов меньше ожидаемого количества, в графе есть цикл: ограничения противоречат друг другу, поэтому верните пустую строку.
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)Обработка крайних случаев: обе задачи
В обеих задачах — «Лестнице слов II» и «Инопланетном словаре» — есть неочевидные крайние случаи, которые приводят к неправильным ответам, если их не обработать:
- «Лестница слов II»: beginWord и endWord совпадают (верните
[[beginWord]]или последовательность длины 1). endWord отсутствует в wordList (верните пустой результат). Пути не существует (верните пустой результат). - «Инопланетный словарь»: повторяющиеся слова duplicate (не извлекайте ограничение). Одно слово (верните все уникальные символы). Цикл в ограничениях (верните ''). Одно слово является более длинным префиксом следующего (некорректные входные данные, верните ''). Все символы изолированы (верните любой порядок).
# 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()Анализ сложности: обе задачи
Сложность «Лестницы слов II»: этап BFS выполняется за O(n × L × 26), где n — количество слов в списке, а L — длина слова. Для каждого слова на каждом уровне BFS мы создаём 26L возможных слов и проверяем их наличие в множестве слов, выполняя каждую проверку за O(1). Сложность этапа DFS составляет O(K × L), где K — количество кратчайших путей; теоретически оно может быть экспоненциальным.
Сложность «Инопланетного словаря»: построение графа выполняется за O(C), где C — общее количество символов во всех словах. Топологическая сортировка выполняется за O(V + E), где V — количество уникальных символов, а E — количество ограничений порядка. Общая сложность составляет O(C), то есть O(общего количества символов во входных данных).
# 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()Итоговый шаблон: два многоразовых решения
Обе задачи обучают многоразовым шаблонам. «Лестница слов II» = BFS для расстояний + DFS для восстановления путей: этот шаблон используется везде, где необходимо найти все кратчайшие пути в невзвешенном графе. Постройте карту предшественников во время BFS, затем выполните обратный проход от пункта назначения к исходной вершине.
«Инопланетный словарь» = извлечение рёбер + топологическая сортировка: этот шаблон используется, когда дана отсортированная последовательность и необходимо вывести лежащие в её основе правила порядка. Извлеките ориентированные ограничения из соседних пар, затем примените алгоритм Кана. При обнаружении цикла возвращайте пустую строку: порядок невозможен.
# 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)Как обрести уверенность в решении сложных задач
Сначала сложные задачи кажутся невозможными, но с правильной ментальной моделью становятся доступными. Ключевые идеи:
- Разделяйте ответственность: решайте каждую подзадачу независимо, прежде чем соединять решения
- Знайте свои строительные блоки: BFS/DFS, топологическая сортировка, алгоритм Дейкстры, таблицы DP — сложные задачи объединяют их неочевидным образом
- Начинайте с примеров: вручную разберите задачу на небольшом примере, чтобы обнаружить лежащую в основе структуру
- Проверяйте подзадачи: после реализации этапа 1 (построения графа) выведите граф и вручную проверьте его перед переходом к этапу 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')Быстрая проверка
Проверьте, насколько хорошо вы понимаете концепции структур данных и алгоритмов — подготовки к собеседованию по программированию, изученные на этом уроке.
Итоги урока
На этом уроке вы узнали: в «Лестнице слов II» BFS строит карту предшественников всех вершин кратчайших путей, а затем обратный проход DFS перечисляет все кратчайшие пути, двигаясь по этой карте от конца к началу, «Инопланетный словарь» извлекает ориентированные ограничения из соседних пар слов и применяет топологическую сортировку Кана для упорядочивания символов, возвращая пустую строку при обнаружении цикла, а также сложные задачи разбиваются на несколько подзадач — построение графа, поиск расстояний и восстановление путей, — каждая из которых решается независимо с помощью знакомых алгоритмов. Теперь вы завершили полный курс подготовки к собеседованиям по DSA. Уверенно применяйте на собеседованиях все шаблоны и методы из этого раздела.
Часто задаваемые вопросы
Урок «Разбор сложных задач: «Лестница слов II» и «Инопланетный словарь»» бесплатный?
Да — полный текст урока «Разбор сложных задач: «Лестница слов II» и «Инопланетный словарь»» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DSA Interview Prep, подпишись на CoddyKit PRO. Курс DSA Interview Prep содержит 4 уроков всего.
Чему я научусь в уроке «Разбор сложных задач: «Лестница слов II» и «Инопланетный словарь»»?
Решите две сложные задачи от начала до конца — «Лестницу слов II» с BFS и перебором с возвратом и «Инопланетный словарь» с топологической сортировкой — с подробным объяснением Ты практикуешь DSA Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DSA Interview Prep?
Предыдущий опыт не требуется. DSA Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Разбор сложных задач: «Лестница слов II» и «Инопланетный словарь»»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DSA Interview Prep?
Да. Каждый урок DSA Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Шпаргалка по распознаванию шаблонов
- Пробное собеседование с ограничением времени: простые и средние задачи
- Обработка крайних случаев и общение на собеседовании
- Разбор сложных задач: «Лестница слов II» и «Инопланетный словарь»