Разбор задачи Word Break II: как разбить строку на слова из словаря и вернуть все возможные предложения. Алгоритм на Python с бэктрекингом и мемоизацией.
Задача Word Break II — классический пример комбинаторного перебора, который часто встречается на собеседованиях в FAANG. Вы научитесь генерировать все возможные разбиения строки на слова из заданного словаря, используя бэктрекинг и мемоизацию. Это развивает навык работы с рекурсией и оптимизации перебора.
Дана строка s и словарь wordDict. Нужно вернуть все возможные предложения (разделённые пробелами), такие что:
Пример:
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
# Результат: ["cat sand dog", "cats and dog"]Попробуем резать строку всеми возможными способами. Для каждого подстроки проверяем, есть ли она в словаре. Если есть — берём её и рекурсивно решаем для оставшейся части. Если нет — пропускаем. Это даёт экспоненциальное дерево рекурсии.
Сложность: O(2^n) по времени, O(n) по памяти (глубина стека).
Вместо того чтобы генерировать все разбиения заранее, на каждом шаге перебираем все возможные подстроки, начиная с текущего индекса. Если подстрока есть в словаре (используем HashSet для O(1) проверки), то добавляем её в текущее предложение и рекурсивно обрабатываем оставшуюся строку. Это отсекает множество заведомо неверных путей.
Ключевое наблюдение: если на индексе i подстрока s[i:j] не в словаре, то все ветки, которые начинаются с этой подстроки, можно не рассматривать.
from typing import List
def wordBreak(s: str, wordDict: List[str]) -> List[str]:
word_set = set(wordDict) # для быстрого поиска
result = []
def backtrack(start: int, path: List[str]):
# если дошли до конца строки — сохраняем предложение
if start == len(s):
result.append(" ".join(path))
return
# перебираем все возможные концы подстроки
for end in range(start, len(s)):
word = s[start:end+1]
if word in word_set:
# выбираем слово
path.append(word)
# рекурсивно продолжаем
backtrack(end+1, path)
# откатываемся (backtrack)
path.pop()
backtrack(0, [])
return resultПусть s = "catsanddog", словарь {"cat", "cats", "and", "sand", "dog"}.
Начинаем с индекса 0:
Из индекса 3 (после "cat"):
Из индекса 7:
Аналогично из индекса 4 (после "cats"):
Без мемоизации одна и та же подстрока (например, "dog") может обрабатываться многократно. Чтобы избежать этого, используем мемоизацию: запоминаем все предложения для каждого стартового индекса.
from typing import List, Dict
def wordBreak(s: str, wordDict: List[str]) -> List[str]:
word_set = set(wordDict)
memo: Dict[int, List[str]] = {}
def backtrack(start: int) -> List[str]:
if start in memo:
return memo[start]
# если дошли до конца, возвращаем список с пустой строкой (база)
if start == len(s):
return [""]
sentences = []
for end in range(start, len(s)):
word = s[start:end+1]
if word in word_set:
# получаем все предложения для остатка
for suffix in backtrack(end+1):
# если суффикс пустой, то просто слово, иначе слово + пробел + суффикс
if suffix:
sentences.append(word + " " + suffix)
else:
sentences.append(word)
memo[start] = sentences
return sentences
return backtrack(0)Теперь каждая подстрока вычисляется только один раз. Сложность в худшем случае всё ещё экспоненциальна, но на практике мемоизация значительно ускоряет решение.
Когда вы слышите «вернуть все возможные разбиения/комбинации», сразу думайте о бэктрекинге. Если задача включает словарь — используйте HashSet. Если есть повторяющиеся подзадачи — добавляйте мемоизацию. Попробуйте решить эту задачу самостоятельно на LeetCode (Word Break II) и сравните своё решение с приведённым.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →