Alien Dictionary
Список слов отсортирован по алфавиту, порядок букв в котором вам неизвестен: 26 строчных английских букв расположены в некотором тайном порядке. Слова сравниваются обычным образом. Решает первая позиция, в которой два слова различаются, а если одно слово является началом другого, первым идёт более короткое слово.
Верните буквы, которые встречаются в словах, одной строкой в алфавитном порядке. Если подходят несколько порядков, верните тот, который идёт первым в обычном словарном порядке. Если ни один порядок не подходит, верните "invalid".
Функция
- wordsstring-array
- слова, отсортированные в неизвестном алфавите
- Возвращаетstring
- буквы в порядке от меньшего к большему, который подходит, или «недопустимо»
Ограничения
1 ≤ words.length ≤ 50001 ≤ words[i].length ≤ 10- Каждое слово содержит только строчные английские буквы.
- Одно и то же слово может встречаться несколько раз.
Примеры
- Ввод
- words = ["tea", "ten", "ate", "act", "cat"]
- Вывод
- "etacn"
- Пояснение
teaпередtenставит a перед n. В остальных парах t стоит перед a, t перед c, а a перед c. Ни одно правило не упоминает e, поэтому самый короткий порядок начинается с него: e, t, a, c, n.
- Ввод
- words = ["bat", "tab", "tub", "bus"]
- Вывод
- "invalid"
- Пояснение
batпередtabставит b перед t, аtubпередbusставит t перед b. Оба условия не могут выполняться одновременно, поэтому ни один порядок не подходит.
- Ввод
- words = ["cooking", "cook"]
- Вывод
- "invalid"
- Пояснение
cook— это начало словаcooking, поэтому в любом алфавите оно стоит первым. В списке оно стоит вторым, и никакой порядок букв этого не объясняет.
Дополнительный вопрос
Как определить, является ли порядок подгонки единственным?
Подсказки
Открывайте по одной. Каждая подсказывает чуть больше.
Посмотрите на два соседних слова, например
teaиten. Что они говорят вам об алфавите и какие вопросы оставляют открытыми?Соседняя пара задаёт не более одного правила: в первой позиции, где слова различаются, буква первого слова стоит раньше буквы второго слова. Правила — это рёбра графа, вершинами которого являются буквы, а ответ — это порядок, соблюдающий все рёбра. Обратите внимание на пару, в которой нет различающихся позиций, но первое слово длиннее второго.
Используйте алгоритм Кана: поместите букву, на которую не указывает ни одно правило, удалите её правила и повторите. Храните готовые буквы в минимальной куче и всегда размещайте наименьшую. Если некоторые буквы так и не будут размещены, значит, правила содержат цикл.
Решение
Каждая пара соседних слов задаёт не более одного правила — в первой позиции, в которой они различаются: буква x перед буквой y. Подходящий порядок — это топологический порядок этих правил. Цикл или слово, стоящее перед собственным префиксом, делает список невозможным. Мини-куча, которая всегда помещает наименьшую готовую букву, даёт наименьший порядок.
Попробуй все варианты порядка букв
Верно, но не успевает на самых больших тестах
Идея
Проверь один порядок расположения k различных букв, сравнив каждую соседнюю пару. В первой позиции, где буквы различаются, буква первого слова должна идти раньше; если различий нет, первое слово не должно быть длиннее. Перебирай расположения по возрастанию, начиная с алфавитного, с помощью следующей перестановки; первый подходящий вариант будет наименьшим подходящим порядком.
Для k букв существует k! расположений: для 10 это 3,628,800, а для 26 — около 4 × 10^26; каждая проверка считывает до 5 × 10^4 символов. Если подходящего варианта нет, поиск перебирает их все.
Алгоритм
- Соберите уникальные буквы и отсортируйте их по алфавиту.
- Запишите ранг каждой буквы в текущем расположении.
- Проверьте каждую соседнюю пару: в первой позиции, где буквы различаются, буква первого слова должна иметь меньший ранг; если различий нет, первое слово не должно быть длиннее.
- Если все пары подходят, верните расположение; в противном случае перейдите к следующему большему.
- Если следующего расположения нет, верните
"invalid".
from itertools import permutations
def fits(words, rank):
# The list is sorted under rank when every neighbouring pair is in order.
for first, second in zip(words, words[1:]):
for x, y in zip(first, second):
if x != y:
if rank[x] > rank[y]:
return False
break
else:
# One word starts the other: the shorter must come first.
if len(first) > len(second):
return False
return True
def alienOrder(words):
letters = sorted(set("".join(words)))
# permutations() of a sorted list yields the orders from smallest to largest,
# so the first order that fits is the answer.
for order in permutations(letters):
rank = {ch: i for i, ch in enumerate(order)}
if fits(words, rank):
return "".join(order)
return "invalid"Алгоритм Кана с минимальной кучей
Идея
Для пары соседей учитывается только первое различие: act перед cat означает, что a стоит перед c, а следующие за ней буквы никогда не сравниваются. Поэтому каждая пара задаёт не более одного ребра.
Алгоритм Кана превращает рёбра в порядок: подсчитай входящие рёбра для каждой буквы, помести букву, у которой счётчик равен 0, удали исходящие из неё рёбра и повторяй. У буквы в цикле всегда остаётся ребро от предыдущей буквы в цикле, поэтому её никогда не поместят. Если букв поместили меньше, чем всего есть, ответ — "invalid".
Храни готовые буквы в мин-куче и помещай наименьшую. Любой подходящий порядок начинается с готовой буквы, поэтому это наименьшая возможная первая буква; её добавление удаляет только рёбра, поэтому она не блокирует другие буквы. В первом примере в начале готовы e и t, и первой идёт e. Обычная очередь даёт допустимый порядок, но не всегда наименьший.
Алгоритм
- Отметьте все буквы, встречающиеся в словах.
- Для каждой пары соседних слов найдите первую отличающуюся позицию и добавьте ребро между соответствующими буквами один раз. Если различий нет, а первое слово длиннее, верните
"invalid". - Подсчитайте количество входящих рёбер и поместите каждую отмеченную букву с нулевой степенью входа в минимальную кучу.
- Извлеките наименьшую букву и добавьте её. Уменьшите счётчик каждой буквы, в которую она указывает, и добавьте в кучу все буквы, у которых счётчик стал равен 0.
- Верните расположенные буквы, если были расположены все отмеченные буквы; в противном случае верните
"invalid".
import heapq
def alienOrder(words):
# Letters are numbered 0 for 'a' up to 25 for 'z'.
present = [False] * 26
for word in words:
for ch in word:
present[ord(ch) - 97] = True
# before[a][b] is True once you know letter a comes before letter b.
before = [[False] * 26 for _ in range(26)]
indegree = [0] * 26
for first, second in zip(words, words[1:]):
for x, y in zip(first, second):
if x != y:
# Only the first difference tells you anything.
a, b = ord(x) - 97, ord(y) - 97
if not before[a][b]:
before[a][b] = True
indegree[b] += 1
break
else:
# No difference: one word starts the other, so the shorter must come first.
if len(first) > len(second):
return "invalid"
# A min-heap of the letters with nothing left before them.
heap = [c for c in range(26) if present[c] and indegree[c] == 0]
heapq.heapify(heap)
order = []
while heap:
c = heapq.heappop(heap)
order.append(chr(c + 97))
for nxt in range(26):
if before[c][nxt]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
heapq.heappush(heap, nxt)
# A letter on a cycle never gets down to indegree 0, so it is never placed.
if len(order) < sum(present):
return "invalid"
return "".join(order)
Ловушки и крайние случаи
- Выбирать из пары больше одного правила. Чтение второй позиции в
actиcatдобавляет c перед a, и в первом примере возникает ложный цикл. - Пропускать ловушку с префиксом. В
cookingпередcookнет различающихся букв, поэтому цикл, который обрабатывает только различия, возвращает порядок. - Не учитывать буквы, которые не встречаются ни в одном правиле. В первом примере ни одно правило не упоминает e, однако эта буква должна быть в ответе.
- Учитывать повторяющееся правило дважды при подсчёте входящей степени, но сохранять его только один раз. Значение для буквы никогда не достигает 0, и корректный список объявляется циклом.
- Считать две одинаковые соседние строки ловушкой с префиксом; повторяющиеся строки упорядочены.
Частые вопросы3
Какова временная сложность задачи «Инопланетный словарь»?
O(C + k²), где C — общее количество символов, а k ≤ 26 — количество различных букв. Один проход по списку находит каждое первое различие, а алгоритм Кана обрабатывает не более k² рёбер. Таблица рёбер занимает O(k²) памяти.
Зачем сравнивать только соседние слова?
Упорядоченность транзитивна: если каждое слово не больше следующего, весь список упорядочен. Поэтому любое правило, которое можно вывести из двух удалённых друг от друга слов, уже следует из соседних пар между ними.
Почему слово перед собственным префиксом недопустимо?
В любом алфавите слово стоит после своего собственного префикса, потому что при сравнении в более коротком слове заканчиваются буквы, прежде чем обнаруживается различие. Это единственный способ, которым список может быть невозможным, даже если в его правилах нет циклов.
Похожие задачи
Задачи на те же идеи. Решите две или три, и приём запомнится.
def alienOrder(words):
# Напишите код здесьТест #1
Тест #2
Тест #3
Аргумент
words = ["tea", "ten", "ate", "act", "cat"]
Ожидаемый вывод
"etacn"