Alien Dictionary
تُرتَّب قائمة من الكلمات وفق أبجدية لا تعرفها: الأحرف الإنجليزية الصغيرة الـ26 بترتيب سري ما. تُقارَن الكلمات بالطريقة المعتادة. يحدِّد أول موضع تختلف فيه كلمتان الترتيب، وإذا كانت إحدى الكلمتين بدايةً للأخرى، تأتي الكلمة الأقصر أولًا.
أعِد الأحرف التي تظهر في الكلمات، كسلسلة واحدة بترتيب الأبجدية. عندما تلائم عدة ترتيبات، أعِد الترتيب الذي يأتي أولًا حسب ترتيب القاموس العادي. عندما لا يلائم أي ترتيب، أعِد "invalid".
الدالة
- wordsstring-array
- الكلمات، مرتبة حسب الأبجدية المجهولة
- تُرجعstring
- الحروف بالترتيب الأصغر الذي يناسب، أو "invalid"
القيود
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 بمقارنة كل زوج من الأحرف المتجاورة. عند أول موضع اختلاف، يجب أن يأتي حرف الكلمة الأولى قبل حرف الكلمة الثانية؛ وإذا لم يوجد اختلاف، فيجب ألا تكون الكلمة الأولى أطول. انتقل تصاعديًا بين الترتيبات بدءًا من الترتيب الأبجدي باستخدام next permutation، وأول ترتيب يجتاز الاختبار هو أصغر ترتيب يحقق الشروط.
للأحرف 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، ولا تُقارَن الحروف التي تأتي بعدها. لذا يعطي كل زوج حافة واحدة على الأكثر.
تحوّل خوارزمية Kahn الحواف إلى ترتيب: احسب الحواف الواردة لكل حرف، وضع حرفًا عدد حوافه الواردة 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 هو عدد الأحرف المميزة. تعثر عملية مرور واحدة على القائمة على كل اختلاف أول، وتزور خوارزمية Kahn ما لا يزيد على k² من الحواف. يتطلب جدول الحواف مساحة O(k²).
لماذا نقارن الكلمات المتجاورة فقط؟
الترتيب خاصية متعدية: إذا كانت كل كلمة أصغر من الكلمة التي تليها أو مساوية لها، تكون القائمة كلها مرتبة. لذلك، فإن أي قاعدة يمكنك استنتاجها من كلمتين متباعدتين تتبع بالفعل من الأزواج المتجاورة بينهما.
لماذا تكون الكلمة التي تسبق بادئتها غير صالحة؟
في كل أبجدية، تأتي الكلمة بعد بادئتها، لأن المقارنة تنفد فيها الحروف في الكلمة الأقصر قبل أن تجد اختلافًا. وهذه هي الطريقة الوحيدة التي يمكن بها أن تكون القائمة مستحيلة من دون وجود أي دورة بين قواعدها.
مسائل مشابهة
مسائل تعتمد على الأفكار نفسها. حلّ اثنتين أو ثلاث منها يثبّت النمط.
def alienOrder(words):
# اكتب الكود هنااختبار #1
اختبار #2
اختبار #3
وسيط
words = ["tea", "ten", "ate", "act", "cat"]
الإخراج المتوقع
"etacn"