Alien Dictionary
Lista słów jest uporządkowana według alfabetu, którego nie znasz: 26 małych angielskich liter w pewnej tajnej kolejności. Słowa porównuje się jak zwykle. Decyduje pierwsza pozycja, na której dwa słowa się różnią, a jeśli jedno słowo jest początkiem drugiego, pierwsze jest krótsze.
Zwróć litery występujące w słowach jako jeden ciąg znaków w kolejności alfabetu. Jeśli pasuje kilka kolejności, zwróć tę, która jest pierwsza w zwykłym porządku słownikowym. Jeśli żadna kolejność nie pasuje, zwróć "invalid".
Funkcja
- wordsstring-array
- słowa posortowane w nieznanym alfabecie
- Zwracastring
- litery w najmniejszej pasującej kolejności lub "invalid"
Ograniczenia
1 ≤ words.length ≤ 50001 ≤ words[i].length ≤ 10- Każde słowo zawiera wyłącznie małe litery alfabetu angielskiego.
- To samo słowo może pojawić się więcej niż raz.
Przykłady
- Wejście
- words = ["tea", "ten", "ate", "act", "cat"]
- Wyjście
- "etacn"
- Wyjaśnienie
teaprzedtenumieszcza a przed n. Pozostałe pary dają t przed a, t przed c i a przed c. Żadna reguła nie wspomina o e, więc najmniejsza kolejność zaczyna się od niego: e, t, a, c, n.
- Wejście
- words = ["bat", "tab", "tub", "bus"]
- Wyjście
- "invalid"
- Wyjaśnienie
batprzedtabumieszcza b przed t, atubprzedbusumieszcza t przed b. Oba warunki nie mogą być spełnione, więc żaden porządek nie pasuje.
- Wejście
- words = ["cooking", "cook"]
- Wyjście
- "invalid"
- Wyjaśnienie
cookto początekcooking, więc w każdym alfabecie pojawia się jako pierwsze. Lista umieszcza je na drugim miejscu, czego nie da się wyjaśnić żadną kolejnością liter.
Pytanie dodatkowe
Jak sprawdzisz, czy kolejność dopasowania jest jedyna?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Przyjrzyj się dwóm sąsiadującym wyrazom, takim jak
teaiten. Co mówią ci o alfabecie, a czego nie rozstrzygają?Sąsiednia para wyznacza co najwyżej jedną regułę: na pierwszej pozycji, na której słowa się różnią, litera pierwszego słowa występuje przed literą drugiego słowa. Reguły są krawędziami grafu utworzonego z liter, a odpowiedzią jest kolejność respektująca każdą krawędź. Zwróć uwagę na parę, w której nie ma żadnej różniącej się pozycji, a pierwsze słowo jest dłuższe.
Użyj algorytmu Kahna: umieść literę, na którą nie wskazuje żadna reguła, usuń jej reguły i powtórz. Przechowuj gotowe litery w kopcu minimalnym i zawsze umieszczaj najmniejszą. Jeśli niektóre litery nigdy nie zostaną umieszczone, reguły zawierają cykl.
Rozwiązanie
Każda para sąsiadujących słów daje co najwyżej jedną regułę, na pierwszej pozycji, na której się różnią: litera x przed literą y. Pasująca kolejność jest porządkiem topologicznym tych reguł. Cykl lub słowo umieszczone przed swoim własnym prefiksem sprawia, że lista jest niemożliwa do ułożenia. Kopiec minimalny, który zawsze umieszcza najmniejszą gotową literę, daje najmniejszą kolejność.
Wypróbuj każdą kolejność liter
Poprawne, ale nie kończy się na największych testach
Intuicja
Przetestuj jedno ustawienie k różnych liter, sprawdzając każdą sąsiednią parę. Na pierwszej różniącej się pozycji litera z pierwszego słowa musi występować wcześniej; jeśli nie ma różnicy, pierwsze słowo nie może być dłuższe. Przeglądaj ustawienia w kolejności rosnącej, zaczynając od alfabetycznej, używając następnej permutacji — pierwsze, które przejdzie test, będzie najmniejszą pasującą kolejnością.
k liter ma k! ustawień: dla 10 jest ich 3,628,800, a dla 26 około 4 × 10^26; każdy test odczytuje do 5 × 10^4 znaków. Gdy nic nie pasuje, wyszukiwanie sprawdza je wszystkie.
Algorytm
- Zbierz różne litery i posortuj je alfabetycznie.
- Zapisz rangę każdej litery w bieżącym układzie.
- Sprawdź każdą sąsiednią parę: na pierwszej różniącej się pozycji litera pierwszego słowa musi mieć niższą rangę; jeśli nie ma różnicy, pierwsze słowo nie może być dłuższe.
- Jeśli każda para spełnia warunek, zwróć układ; w przeciwnym razie przejdź do następnego większego.
- Gdy nie ma następnego układu, zwróć
"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"Algorytm Kahna z kopcem minimalnym
Intuicja
W parze sąsiadujących słów liczy się tylko pierwsza różnica: act przed cat oznacza, że a występuje przed c, a kolejne litery nigdy nie są porównywane. Zatem każda para daje najwyżej jedną krawędź.
Algorytm Kahna zamienia krawędzie na kolejność: policz krawędzie wchodzące dla każdej litery, umieść literę, której licznik wynosi 0, usuń jej krawędzie wychodzące i powtórz. Litera należąca do cyklu zawsze zachowuje krawędź od poprzedzającej ją litery w cyklu, więc nigdy nie zostanie umieszczona. Jeśli umieszczono mniej liter, niż występuje w danych, odpowiedzią jest "invalid".
Przechowuj gotowe litery w kopcu minimum i umieszczaj najmniejszą. Każda pasująca kolejność zaczyna się od gotowej litery, więc jest to najmniejsza możliwa pierwsza litera, a jej umieszczenie usuwa tylko krawędzie, więc nie blokuje żadnej innej litery. W pierwszym przykładzie e i t są gotowe na początku, więc jako pierwsze trafia e. Zwykła kolejka daje poprawną kolejność, ale nie zawsze najmniejszą.
Algorytm
- Oznacz każdą literę występującą w wyrazach.
- Dla każdej pary sąsiadów znajdź pierwszą pozycję, na której litery się różnią, i dodaj krawędź między tymi literami, tylko raz. Jeśli nie ma różnicy, a pierwszy wyraz jest dłuższy, zwróć
"invalid". - Policz krawędzie wchodzące i umieść każdą oznaczoną literę o wartości 0 na kopcu minimalnym.
- Zdejmij najmniejszą literę i dołącz ją. Zmniejsz wartość dla każdej litery, do której prowadzi krawędź z tej litery, i umieść na kopcu każdą, której wartość osiągnie 0.
- Zwróć umieszczone litery, jeśli umieszczono wszystkie oznaczone litery; w przeciwnym razie zwróć
"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)
Pułapki i przypadki brzegowe
- Branie więcej niż jednej reguły z pary. Odczytanie drugiej pozycji w
acticatdodaje c przed a, przez co pierwszy przykład staje się fałszywym cyklem. - Przeoczenie pułapki prefiksu.
cookingprzedcooknie ma różniącej je litery, więc pętla obsługująca tylko różnice zwraca kolejność. - Pominięcie liter, które nie pojawiają się w żadnej regule. W pierwszym przykładzie żadna reguła nie wspomina o e, a mimo to e należy do odpowiedzi.
- Dwukrotne zliczenie powtórzonej reguły w stopniu wejściowym, ale zapisanie jej tylko raz. Stopień wejściowy litery nigdy nie osiąga 0, więc poprawna lista zostaje zgłoszona jako cykl.
- Uznanie dwóch jednakowych sąsiadujących słów za pułapkę prefiksu; powtórzone słowo jest w porządku.
Najczęstsze pytania3
Jaka jest złożoność czasowa problemu Alien Dictionary?
O(C + k²), gdzie C to łączna liczba znaków, a k ≤ 26 to liczba różnych liter. Jedno przejście przez listę znajduje każdą pierwszą różnicę, a algorytm Kahna odwiedza najwyżej k² krawędzi. Tablica krawędzi zajmuje O(k²) pamięci.
Dlaczego porównywać tylko sąsiednie słowa?
Posortowanie jest przechodnie: jeśli każde słowo jest mniejsze lub równe następnemu, cała lista jest posortowana. Zatem każda reguła, którą można odczytać z dwóch odległych słów, wynika już z sąsiadujących ze sobą par między nimi.
Dlaczego słowo przed własnym prefiksem jest nieprawidłowe?
W każdym alfabecie słowo występuje po swoim własnym prefiksie, ponieważ porównywanie kończy się na literach krótszego słowa, zanim znajdzie różnicę. To jedyny sposób, w jaki lista może być niemożliwa, mimo że wśród jej reguł nie ma żadnego cyklu.
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
def alienOrder(words):
# Napisz kod tutajTest #1
Test #2
Test #3
Argument
words = ["tea", "ten", "ate", "act", "cat"]
Oczekiwany wynik
"etacn"